创建线程池的方法有两种,一种是手动创建,一种是自动创建,本文将分别介绍这两种方式的简单使用。
一、自动创建线程池自动创建线程池主要是调用jdk提供的Executors类提供的方法。
1、newFixedThreadPool()方法它创建一个可重用固定个数的线程池,以共享的无界队列运行这些线程,构造方法如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
此方法建了一个核心线程数和最大线程数相等的线程池,它的数量也就是我们传入的参数,需要注意的是,LinkedBlockingQueue队列容量是2147483647,假如任务的处理速度比较慢,那么随着请求的增多,队列中堆积的任务也会越来越多,最终这些任务占用大量内存,会发生内存泄漏,会造成很严重的后果。
2、newSingleThreadExecutor()方法它创建一个单线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO)执行,构造方法如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
它和第一种方法的隐患是一样的,只不过在构造时将核心线程数和最大线程数直接设置成了1,但是任务队列仍是无界的LinkedBlockingQueue,仍可能会造成内存泄漏问题。
3、newCachedThreadPool()方法它是可缓存线程池,先查看池中有没有以前建立的线程,如果有就直接使用,如果没有就创建一个新的线程加入池中,通常用于执行一些生存期很短的异步型任务,构造方法如下:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
这个构造方法中使用的任务队列是SynchronousQueue,SynchronousQueue本身并不存储任务,而是对任务直接进行转发。但它的最大线程数被设置成了Integer.MAX_VALUE,由于CachedThreadPool 并不限制线程的数量,当任务数量特别多的时候,就可能会导致创建非常多的线程,最终超过了操作系统的上限而无法创建新线程,或者导致内存不足。
4、newScheduledThreadPool()方法它创建一个定长线程池,支持定时及周期性任务执行,构造方法如下:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
5、newSingleThreadScheduledExecutor()方法
它的构造方法如下:
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
第四和第五种方式都是使用ScheduledThreadPoolExecutor(int corePoolSize)作为核心构造方法,其实质是一种延迟队列,同时也定义了最大线程数为Integer.MAX_VALUE,同样有可能会出现内存泄漏的状况。
二、自定义线程池 1、ThreadPoolExecutor构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
各参数的含义如下:
- corePoolSize :核心线程数,即使空闲时仍保留在池中的线程数,除非设置allowCoreThreadTimeOut
- maximumPoolSize : 池中允许创建的最大线程数
- keepAliveTime :当线程数大于内核时,新进的任务会被存储到队列中,当这些线程等待时间超过这个阈值,就会被回收,等待执行新任务。
- unit : keepAliveTime参数的时间单位
- workQueue : 用于在执行任务之前使用的队列。这个队列将仅保存execute方法提交的Runnable任务。
- handler :阻止策略,执行被阻止时使用的处理程序,因为达到线程限制和队列容量
线程池的执行逻辑是这样的:
- 当线程池接收到提交的新任务,首先判断核心线程数目是否已经达到了限定值,如果没有,不论其它工作线程是否处于空闲状态,线程池都会创建一个新线程处理该任务
- 当核心线程数目已满足限定值,工作队列未满时,线程池会将新任务放入执行队列进行等待
- 当工作队列已满,当前线程数超过核心线程数,小于允许创建的最大线程数时,线程池会创建一个新线程处理该任务
- 若当前线程数超过允许创建的最大线程数时,线程池会按照指定的策略处理无法执行的任务
一般分为直接执行队列、有界任务队列、无界任务队列、优先任务队列。
(1)SynchronousQueue直接执行队列:SynchronousQueue是一个特殊的BlockingQueue,它没有容量,每执行一个插入操作就会阻塞。
使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据设置的handler执行拒绝策略。
(2)ArrayBlockingQueue有界队列:当有新的任务需要执行,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。
它的执行逻辑与前面提到的部分基本一致,值得注意的是,如果队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下。
(3)LinkedBlockingQueue无界队列:无界队列的构造函数如下,LinkedBlockingQueue队列容量是2147483647,前面提到,假如任务的处理速度比较慢,那么随着请求的增多,队列中堆积的任务也会越来越多,最终这些任务占用大量内存,使用时要做好处理,避免出现资源耗尽的情况。
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node(null);
}
(4)PriorityBlockingQueue优先任务队列:该队列的默认数组大小是11,但是其允许的最大容量也是很大的。在构造时,可以传入一个比较器Comparator,用来比较队列内各任务的优先级。
private static final int DEFAULT_INITIAL_CAPACITY = 11;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
public PriorityBlockingQueue(int initialCapacity) {
this(initialCapacity, null);
}
public PriorityBlockingQueue(int initialCapacity,
Comparator super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
this.comparator = comparator;
this.queue = new Object[initialCapacity];
}
3、Reject阻止策略
当提交的任务数大于(workQueue.size() + maximumPoolSize),就会触发线程池的拒绝策略。
RejectedExecutionHandler 是拒绝策略的接口,有四个实现类
-
AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;
-
CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行,即从哪个线程来的,哪个线程处理,;
-
DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
-
DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
4、自定义线程池实现
(1)模拟自定义拒绝策略,实现RejectedExecutionHandler
public class MyRejected implements RejectedExecutionHandler{
public MyRejected(){
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("当前被拒绝任务为:" + r.toString());
}
}
(2)task任务类
public class MyTask implements Runnable {
private int taskId;
private String taskName;
public MyTask(int taskId, String taskName){
this.taskId = taskId;
this.taskName = taskName;
}
@Override
public void run() {
try {
System.out.println("run taskId =" + this.taskId);
Thread.sleep(5*1000);
System.out.println("end taskId =" + this.taskId);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String toString(){
return Integer.toString(this.taskId);
}
}
(3)自定义线程池实现
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, //coreSize
2, //MaxSize
60, //60
TimeUnit.SECONDS,
new ArrayBlockingQueue(3) //指定一种队列 (有界队列)
, new MyRejected() //自定义拒绝策略
//, new DiscardOldestPolicy() //可以使用默认拒绝策略
);
MyTask mt1 = new MyTask(1, "任务1");
MyTask mt2 = new MyTask(2, "任务2");
MyTask mt3 = new MyTask(3, "任务3");
MyTask mt4 = new MyTask(4, "任务4");
MyTask mt5 = new MyTask(5, "任务5");
MyTask mt6 = new MyTask(6, "任务6");
pool.execute(mt1);
pool.execute(mt2);
pool.execute(mt3);
pool.execute(mt4);
pool.execute(mt5);
pool.execute(mt6);
pool.shutdown();
}
控制台输出如下:
run taskId =5
当前被拒绝任务为:6
run taskId =1
run taskId =2
run taskId =3
run taskId =4
分析如下:
(1)当第1个任务到达时,线程池创建一个新线程去处理该任务
(2)当第2个任务到达时,当前线程核心线程数已满足,但是未满足最大线程数,因此线程池会再创建一个新线程去处理该任务
(3)当第3/4/5个任务到达时,因已满足最大线程数,线程池将它们放入队列等候处理
(4)当第6个任务到达时,因已满足最大线程数,且队列已满,线程池无法处理该任务,执行自定义拒绝策略
三、springboot使用自定义线程池
1、配置线程池
@Configuration
@EnableAsync //开启多线程
public class ThreadPoolConfig {
@Bean("taskExecutor")
public Executor asyncServiceExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(5);
// 设置最大线程数
executor.setMaxPoolSize(20);
//配置队列大小
executor.setQueueCapacity(Integer.MAX_VALUE);
// 设置线程空闲时间,当超过核心线程之外的线程在空闲到达之后会被销毁(秒)
executor.setKeepAliveSeconds(60);
// 设置默认线程名称
executor.setThreadNamePrefix("ThreadExcutor");
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
//拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
//执行初始化
executor.initialize();
return executor;
}
}
2、为@Async指定线程池
@Component
@Async("taskExecutor") //要和线程池bean中的对应
public class ThreadService {
//期望此操作在线程池执行 ,不会影响原有的主线程
try {
Thread.sleep(5000);
System.out.println("业务处理完成了....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
参考文章:
https://blog.csdn.net/yongbutingxide/article/details/122285958
https://blog.csdn.net/xu962336414/article/details/104892640
https://blog.51cto.com/zhangfengzhe/1881644
https://www.icode9.com/content-4-1312968.html
https://blog.csdn.net/xiaoxiaodaxiake/article/details/124653659
https://www.cnblogs.com/dafanjoy/p/9729358.html



