前言
当我们需要进行大量操作时,单线程同步操作的速率明显不能满足我们的需求。这时我们就要使用多线程技术,用多个线程同时处理多个任务,如各个线程之间又没有数据交流,更是可以使用异步方法加快任务的执行。
而线程的创建和销毁都需要占用资源,这时就要用到线程池技术。
一、线程池的运行流程
线程池的作用就是预先创建几个线程(核心线程),核心线程的生命周期与线程池一致,随着线程池的创建而创建,随着线程池的销毁而销毁。
当核心线程被占满,新提交的任务会排列在阻塞队列中,核心线程空闲后,会立即处理阻塞队列中的任务。
当线程执行完分配到的任务,又会回到线程池中等待下一次调用。
当阻塞队列为空、线程空闲超过一个特定的时间,线程池就会销毁多余的线程,保留还在运行的线程,且至少保留“核心线程数”个线程。
所以,使用线程池技术只需要定义线程池中的几个属性,线程池会自动为你完成 分配任务、创建线程、空闲线程复用、空闲线程回收 等任务。
二、线程池实现类ThreadPoolTaskExcutor
1.引入库
代码如下(示例):
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
2.线程池七大参数
线程池七大参数分别是corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler
(1)corePoolSize:线程池中常驻核心线程数
(2)maximumPoolSize:线程池能够容纳同时执行的最大线程数
(3)keepAliveTime:多余的空闲线程存活时间
(4)unit:keepAliveTime的时间单位
(5)workQueue:任务队列,被提交但尚未执行的任务
(6)threadFactory:表示生成线程池中的工作线程的线程工厂
(7)handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何拒绝。
3.工具类代码
实例:
public class ExecutorUtil {
//获取当前机器的核数
// public static final int cpuNum = Runtime.getRuntime().availableProcessors();
public static Executor getAsyncExecutor(Integer corePoolSize, Integer maxPoolSize) {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setMaxPoolSize(maxPoolSize);//最大线程大小
taskExecutor.setCorePoolSize(corePoolSize);//核心线程大小
//队列最大容量,默认为Integer的最大值,全程异步队列设为0
// taskExecutor.setQueueCapacity(0);
taskExecutor.setQueueCapacity(1000);
//当提交的任务个数大于QueueCapacity,就需要设置该参数,但spring提供的都不太满足业务场景,可以自定义一个,也可以注意不要超过QueueCapacity即可
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);// 设置关闭时是否等待任务完成
taskExecutor.setAwaitTerminationSeconds(60);// 等待终止的秒数,就是上面说的空闲多长时间会销毁
taskExecutor.setThreadNamePrefix("Thread-");// 设置线程名前缀
taskExecutor.initialize();
return taskExecutor;
}
}
4.设置参数技巧
(1)核心线程数和最大线程数:
CPU密集型:
最大线程数 = CPU核心数 + 1
核心线程数 = 最大线程数 * 20%
io密集型:
最大线程数 = CPU核心数 * 2
核心线程数 = 最大线程数 * 20%
最好让有丰富经验的人根据实际情况指定两个参数。
(2)阻塞队列:
如果全程异步可设置为0(线程之间完全独立,不存在数据交换,也无需等待同步信号返回)。
如果设置一个非零的数比如1000,阻塞队列的最大数量就为1000,加上最大线程数,也就保持在1000左右,有效限制并发量。
如果不设置阻塞队列,则默认为Integer的最大值,不建议这样设置,当并发量小的时候,无法填满阻塞队列,也就无法启用非核心线程;当能填满阻塞队列时,并发量就很大了,服务器的压力也会很大。所以还是建议设置一个阻塞队列长度,最好还是根据并发量和计算机性能设置一个合适的值。
5.具体使用
import org.example.util.ExecutorUtil;
import java.util.concurrent.Executor;
Executor executor = ExecutorUtil.getAsyncExecutor(CORE_POOL_NUM, MAX_POOL_NUM);
……// 此处应该是一个循环体for或while,重复提交任务到线程池
executor.execute(() -> {
……// 需要并发执行的方法
});
总结
本文描述了线程池的原理与基本流程,提供了模板代码,希望能在各位读者需要时派上用场!