花几分钟看懂如何配置线程池的核心线程数

目录

一、背景

二、什么是CPU核数和逻辑处理器数

三、CPU密集型和IO密集型

四、线程池核心线程数确定

五、代码实现-ThreadPoolTaskExecutor


一、背景

为了实现异步,需要将任务开新的线程去处理调三方接口等,从而不影响主线程的工作。而配置线程池可以方便线程的管理,减少线程创建、摧毁带来的性能消耗和提高响应速度等。这篇文章将简单介绍如何配置线程池。

 

二、什么是CPU核数和逻辑处理器数

ctrl+alt+delete打开任务管理器,打开性能,如下图我的机器配置,可以发现右下角有内核和逻辑处理器

内核指的就是CPU核数逻辑处理器是线程数-最大可以并行的线程数(这里我搜索了无数的资料,验证了CPU核数和上述的内核是不同的描述而已,它两相等。如果还错,那真打脸了..)

有了这个知识,下面还要简单介绍一下CPU密集型和IO密集型

 

三、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

其他参数不了解的可以参考我之前的文章介绍:超详细的线程池介绍

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去初始化线程池。


线程池核心线程数配置到此已经写完,有啥问题可以留言探讨。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值