赞
踩
目录
为了实现异步,需要将任务开新的线程去处理调三方接口等,从而不影响主线程的工作。而配置线程池可以方便线程的管理,减少线程创建、摧毁带来的性能消耗和提高响应速度等。这篇文章将简单介绍如何配置线程池。
ctrl+alt+delete打开任务管理器,打开性能,如下图我的机器配置,可以发现右下角有内核和逻辑处理器
内核指的就是CPU核数,逻辑处理器是线程数-最大可以并行的线程数(这里我搜索了无数的资料,验证了CPU核数和上述的内核是不同的描述而已,它两相等。如果还错,那真打脸了..)
有了这个知识,下面还要简单介绍一下CPU密集型和IO密集型
线程任务可以分为CPU密集型和IO密集型。(平时开发基本上都是IO密集型任务)
CPU密集型任务的特点是进行大量的计算,消耗CPU资源,比如计算圆周率、视频高清解码等。这种任务操作都是比较耗时间的操作,任务越多花在任务切换的时间就越多,CPU执行任务效率就越低。所以,应当减少线程的数量,CPU密集型任务同时进行的数量应当等于CPU的核心数,上述我的电脑为8。
IO密集型的任务的特点是涉及到网络(调用三方接口)、磁盘IO(文件操作)等。这类任务操作是CPU消耗很少,任务大部分时间都在等待IO操作完成(IO的速度远低于CPU和内存的速度)。对于这种任务,任务越多,CPU效率越高,但是也有限度。我们开发接口时,像调别的应用接口,基本逻辑处理等,基本上都是属于IO密集型任务。
刚才提到了,CPU密集型尽量配置少的线程,核心线程配置:CPU核数。而IO线程池应配置多的线程,核心线程配置:CPU核数*2。这里IO密集型还有一种情况是线程易阻塞型的,需要计算阻塞系数,他是这么配置线程核心线程数的:CPU核数 / 1 – 阻塞系数(0.8~0.9之间)。
Java给我们提供了获取逻辑处理器-也就是最大并行可以执行的线程数的API,如下图我的机器为16
核心线程计算:根据第三节的知识,及我的第一节应用背景是异步调三方接口,属于IO密集型,核心线程配置:CPU核数*2=8*2=16。(这里不考虑另一种情况配置)
其实API直接计算得到的值就是16,刚好满足,下面是实现代码,用的是ThreadPoolTaskExecutor,底层封装了ThreadPoolExecutor。后面专门写篇文章介绍一下。
其他参数不了解的可以参考我之前的文章介绍:超详细的线程池介绍。
ThreadPoolTaskExecutor类不了解的或者它与ThreadPoolExecutor的区别参考另一篇文章:ThreadPoolTaskExecutor的点滴理解
线程池配置代码:(这里是通过Bean的方式)
- @Component
- public class ThreadPoolConfig {
- /**
- * 线程池名称
- */
- private static final String TASK_THREADPOOL_NAME = "task_threadpool_test";
-
- /**
- * 线程池配置
- * @return 线程池
- */
- @Bean(TASK_THREADPOOL_NAME)
- public Executor asyncThreadPoolExecutor() {
- // 获得运行机器 逻辑处理器核数(其实就是线程数)
- Integer availibleNum = Runtime.getRuntime().availableProcessors();
- // 内部还是使用了ThreadPoolExecutor,只是参数配置更加方便
- ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
- // 核心参数
- executor.setCorePoolSize(availibleNum);
- // 配置最大线程数 线程数*2
- executor.setMaxPoolSize(availibleNum * 2);
- // 配置队列容量 队列:不配置容量,用SynchronousQueue,配置用LinkedBlockingQueue。 (BlockingQueue)(queueCapacity > 0 ? new LinkedBlockingQueue(queueCapacity) : new SynchronousQueue());
- executor.setQueueCapacity(100);
- // 配置非核心线程超时释放时间(核心线程默认不允许回收)
- executor.setKeepAliveSeconds(60);
- // 配置线程名称前缀
- executor.setThreadNamePrefix(TASK_THREADPOOL_NAME);
- // 配置拒绝策略,CallerRunsPolicy:当拒绝时由调用线程处理该任务
- executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
- return executor;
- }
- }
-
线程池使用代码:
- @Component
- public class MsgTaskUtils {
-
- @Autowired
- @Qualifier("task_threadpool_test")
- private Executor asyncExecutor;
-
- public void dealTask() {
- // 模拟任务
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- }
- };
- // 通过线程池方式执行
- asyncExecutor.execute(runnable);
- }
- }
如果不用Bean的方式如何实现?代码如下(需要自己调用initalize初始化线程池)
- /**
- * 自己new的,不会自己初始化,需要调用initialize才能用,否则报错
- * java.lang.IllegalStateException: ThreadPoolTaskExecutor not initialized
- */
- public static ThreadPoolTaskExecutor asyncThreadPoolExecutor() {
- // 获得运行机器 逻辑处理器核数(其实就是线程数)
- Integer availibleNum = Runtime.getRuntime().availableProcessors();
- // 内部还是使用了ThreadPoolExecutor,只是参数配置更加方便
- ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
- // 核心参数
- executor.setCorePoolSize(availibleNum);
- // 配置最大线程数
- executor.setMaxPoolSize(availibleNum * 2);
- // 配置队列容量 队列:不配置容量,用SynchronousQueue,配置用LinkedBlockingQueue。 (BlockingQueue)(queueCapacity > 0 ? new LinkedBlockingQueue(queueCapacity) : new SynchronousQueue());
- executor.setQueueCapacity(100);
- // 配置非核心线程超时释放时间(核心线程默认不允许回收)
- executor.setKeepAliveSeconds(60);
- // 配置线程名称前缀
- executor.setThreadNamePrefix(TASK_THREADPOOL_NAME);
- // 配置拒绝策略,CallerRunsPolicy:当拒绝时由调用线程处理该任务
- executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
- // 不用bean注入方法,需要自己初始化线程池
- executor.initialize();
- return executor;
- }
注意点:如果不是通过Bean的方式实现,那么需要手动去调用initialize方法去初始化线程池,因为ThreadPoolTaskExecutor-Bean实现了InitializingBean接口的Bean会调用,其里面的afterPropertiesSet去初始化线程池。
线程池核心线程数配置到此已经写完,有啥问题可以留言探讨。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。