线程池②——线程池的使用

183 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情

1. 结合CompletableFuture使用线程池

CompletableFuture,结合了Future的优点,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。

CompletableFuture可以传入自定义线程池,否则使用自己默认的线程池,我们习惯做法是自定义线程池,控制整个项目的线程数量,不使用自定义的线程池,做到可控可调

步骤一:声明一个线程池Bean

application.properties

//尽量做到每个业务使用自己配置的线程池
service1.thread.coreSize=10
service1.thread.maxSize=100
service1.thread.keepAliveTime=10

线程池的配置属性类

/**
 * @date: 2022/8/12
 * @FileName: ThreadPoolConfigProperties
 * @author: Yan
 * @Des:
 */
@ConfigurationProperties(prefix = "service1.thread")
@Data
public class ThreadPoolConfigProperties {
    private Integer coreSize;
    private Integer maxSize;
    private Integer keepAliveTime;
}

@ConfigurationProperties注解出现Spring Boot Configuration Annotation Processor not configured提示,若后续运行出错,可以进行如下操作

image-20220815232514359

参考官方文档

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
<project>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-configuration-processor</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

把工程clean一下,然后重新build一下就ok了

步骤二:线程池的配置类config

线程池配置类: 根据不同业务定义不同的线程池配置

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @date: 2022/8/12
 * @FileName: MyService1ThreadConfig
 * @author: Yan
 * @Des: 线程池配置类:根据不同业务定义不同的线程池配置
 */
@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
@Configuration
public class MyService1ThreadConfig {
    @Bean
    public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) {
        return new ThreadPoolExecutor(
                pool.getCoreSize(),
                pool.getMaxSize(),
                pool.getKeepAliveTime(),
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(100000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
    }
}

步骤三:使用

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Test
    void contextLoads() {
        System.out.println("main.................start.....");
        CompletableFuture.runAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
        }, threadPoolExecutor);
        System.out.println("main.................end......");
    }

结果

main.................start.....
main.................end......
当前线程:16
运行结果:5

2. 结合@Async使用线程池

在现实的互联网项目开发中,针对高并发的请求,一般的做法是高并发接口单独线程池隔离处理。可能为某一高并发的接口单独一个线程池

2.1 方式1:默认线程池

使用@Async注解,在默认情况下用的是SimpleAsyncTaskExecutor线程池,该线程池不是真正意义上的线程池

使用此线程池无法实现线程重用,每次调用都会新建一条线程。若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误

步骤1: 自定义一个能查看线程池参数的类

  • 不清楚线程池当时的情况,有多少线程在执行,多少在队列中等待呢?
  • 创建了一个ThreadPoolTaskExecutor的子类,在每次提交线程任务的时候都会将当前线程池的运行状况打印出来

public class VisiableThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {

    private static final Logger logger = LoggerFactory.getLogger(VisiableThreadPoolTaskExecutor.class);

    private void showThreadPoolInfo(String prefix) {
        ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();

        if (null == threadPoolExecutor) {
            return;
        }

        logger.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
                this.getThreadNamePrefix(),
                prefix,
                threadPoolExecutor.getTaskCount(),
                threadPoolExecutor.getCompletedTaskCount(),
                threadPoolExecutor.getActiveCount(),
                threadPoolExecutor.getQueue().size());
    }

    @Override
    public void execute(Runnable task) {
        showThreadPoolInfo("1. do execute");
        super.execute(task);
    }

    @Override
    public void execute(Runnable task, long startTimeout) {
        showThreadPoolInfo("2. do execute");
        super.execute(task, startTimeout);
    }

    @Override
    public Future<?> submit(Runnable task) {
        showThreadPoolInfo("1. do submit");
        return super.submit(task);
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        showThreadPoolInfo("2. do submit");
        return super.submit(task);
    }

    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
        showThreadPoolInfo("1. do submitListenable");
        return super.submitListenable(task);
    }

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        showThreadPoolInfo("2. do submitListenable");
        return super.submitListenable(task);
    }
}

步骤2: 实现AsyncConfigurer类

  • 要配置默认的线程池,要实现AsyncConfigurer类的两个方法
  • 不需要打印运行状况的可以使用ThreadPoolTaskExecutor类构建线程池
/**
 * @date: 2022/8/16
 * @FileName: AsyncThreadConfig
 * @author: Yan
 * @Des:
 */
@Slf4j
@EnableAsync
@Configuration
public class AsyncThread1Config implements AsyncConfigurer {
    /**
     * 定义@Async默认的线程池
     * ThreadPoolTaskExecutor不是完全被IOC容器管理的bean,可以在方法上加上@Bean注解交给容器管理,
     * 这样可以将taskExecutor.initialize()方法调用去掉,容器会自动调用
     *
     * @return
     */
    @Override
    public Executor getAsyncExecutor() {
        int processors = Runtime.getRuntime().availableProcessors();
        //常用的执行器
        //ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //可以查看线程池参数的自定义执行器
        ThreadPoolTaskExecutor taskExecutor = new VisiableThreadPoolTaskExecutor();
        //核心线程数
        taskExecutor.setCorePoolSize(1);
        taskExecutor.setMaxPoolSize(2);
        //线程队列最大线程数,默认:50
        taskExecutor.setQueueCapacity(50);
        //线程名称前缀
        taskExecutor.setThreadNamePrefix("default-ljw-");
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //执行初始化(重要)
        taskExecutor.initialize();
        return taskExecutor;
    }

    /**
     * 异步方法执行的过程中抛出的异常捕获
     *
     * @return
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) ->
                log.error("线程池执行任务发送未知错误,执行方法:{}", method.getName(), ex.getMessage());
    }
}

步骤3:使用

在Service层对特定的需要异步处理的操作,直接添加注解@Async 即可使用到配置的默认线程池

/**
 * @date: 2022/8/16
 * @FileName: ThreadAsyncService
 * @author: Yan
 * @Des:
 */
@Service
@Slf4j
public class ThreadAsyncService {
    /**
     * 默认线程池
     */
    @SneakyThrows
    @Async
    public void defaultThread(){
        long start = System.currentTimeMillis();
        Random random = new Random();
        System.out.println(99999999 * 99999999);
        long end = System.currentTimeMillis();
//        抛出异常
        int i = 1 / 0;
       log.info("使用默认线程池,耗时:" + (end - start) + "毫秒");
    }
}

测试类 ,调用异步处理

    @Autowired
    private ThreadAsyncService threadService;

    @Test
    void contextLoads() throws Exception {
        System.out.println("main.................start.....");
        threadService.defaultThread();
        System.out.println("main.................end......");
    }

运行结果

main.................start.....
2022-08-16 10:23:08.973  INFO 6680 --- [           main] c.y.t.e.VisiableThreadPoolTaskExecutor   : Initializing ExecutorService
2022-08-16 10:23:08.974  INFO 6680 --- [           main] c.y.t.e.VisiableThreadPoolTaskExecutor   : default-ljw-, 2. do submit,taskCount [0], completedTaskCount [0], activeCount [0], queueSize [0]
main.................end......
1674919425
2022-08-16 10:23:08.982 ERROR 6680 --- [  default-ljw-1] c.y.t.config.AsyncThread1Config          : 线程池执行任务发送未知错误,执行方法:defaultThread

可以看到有线程池的相关记录**default-ljw-, 2. do submit,taskCount [0], completedTaskCount [0], activeCount [0], queueSize [0]打印了出来,相关意思可以看步骤1**的VisiableThreadPoolTaskExecutor类的方法

2.2 方式2:指定线程池

由于业务需要,根据业务不同需要不同的线程池

在方式一的基础上进行下面步骤

步骤1:声明一个线程池bean

此时,项目中有两个线程池Bean,一个是方式一声明的AsyncThread1Config,一个是现在这个AsyncThread2Config


/**
 * @date: 2022/8/16
 * @FileName: AsyncThread2Config
 * @author: Yan
 * @Des: 注解@async配置
 */
@Slf4j
@EnableAsync
@Configuration
public class AsyncThread2Config {
    @Bean("service2Executor")
    public Executor service2Executor() {
        //Java虚拟机可用的处理器数
        int processors = Runtime.getRuntime().availableProcessors();
        //定义线程池
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //可以查看线程池参数的自定义执行器
        //ThreadPoolTaskExecutor taskExecutor = new VisiableThreadPoolTaskExecutor();
        //核心线程数
        taskExecutor.setCorePoolSize(processors);
        taskExecutor.setMaxPoolSize(100);
        //线程队列最大线程数,默认:100
        taskExecutor.setQueueCapacity(100);
        //线程名称前缀
        taskExecutor.setThreadNamePrefix("my-ljw-");
        //线程池中线程最大空闲时间,默认:60,单位:秒
        taskExecutor.setKeepAliveSeconds(60);
        //核心线程是否允许超时,默认:false
        taskExecutor.setAllowCoreThreadTimeOut(false);
        //IOC容器关闭时是否阻塞等待剩余的任务执行完成,默认:false(必须设置setAwaitTerminationSeconds)
        taskExecutor.setWaitForTasksToCompleteOnShutdown(false);
        //阻塞IOC容器关闭的时间,默认:10秒(必须设置setWaitForTasksToCompleteOnShutdown)
        taskExecutor.setAwaitTerminationSeconds(10);
        /**
         * 拒绝策略,默认是AbortPolicy
         * AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
         * DiscardPolicy:丢弃任务但不抛出异常
         * DiscardOldestPolicy:丢弃最旧的处理程序,然后重试,如果执行器关闭,这时丢弃任务
         * CallerRunsPolicy:执行器执行任务失败,则在策略回调方法中执行任务,如果执行器关闭,这时丢弃任务
         */
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return taskExecutor;
    }
}

步骤2:使用

@Async("service2Executor")注解指定使用的线程池名称

    @Autowired
    private ThreadAsyncService threadService;

    @Test
    void contextLoads() throws Exception {
        System.out.println("main.................start.....");
        threadService.defaultThread();
        threadService.service2Executor();
        System.out.println("main.................end......");
    }

结果

image-20220816103234780

2.3 @Async+Future获取异步执行结果

异步方法

/**
 * 如果有返回值,返回类型应该为 Future<>
 *
 * @return
 */
@Async
public Future<String> getResult() throws InterruptedException {
    Thread.sleep(3 * 1000);
    return new AsyncResult<>("haha");
}

调用

log.info("异步获取返回值");
Future<String> result = service2.getResult();
//阻塞获取
String s = result.get();