文章目录
1、基本概念
ThreadPoolExecutor类实现了ExecutorService接口和Executor接口,可以设置线程池corePoolSize,最大线程池大小,BlockingQueue,AliveTime,拒绝策略等。
常用构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
....
}
参数 | 含义 |
---|---|
corePoolSize | 线程池核心线程数量。在创建线程池后,默认情况下线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用prestartAllCoreThreads( )或prestartCoreThread( )方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程 |
maximumPoolSize | 线程池最大线程数,它表示在线程池中最多能创建多少个线程; |
keepAliveTime | 当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,传入true,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;该方法你就理解成核心线程是否超时回收 |
unit | 线程所允许的空闲时间单位 |
workQueue | 线程池所使用的缓冲队列,BlockingQueue接口的具体实例 |
threadFactory | 线程工厂,主要用来创建线程,ThreadFactory是一个接口,只有一个方法。通过线程工厂可以对线程的一些属性进行定制,通常在实际开发中都会定义自己的线程工厂,指定有意义的线程名称,方便出问题的时候回溯 |
handler | 线程池对拒绝任务的处理策略 |
2、缓冲队列BlockingQueue
类型 | 说明 |
---|---|
ArrayBlockingQueue | 是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序 |
LinkedBlockingQueue | 一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue,静态工厂方法Executors.newFixedThreadPool()使用了这个队列 |
SynchronousQueue | 一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。 |
PriorityBlockingQueue | 一个具有优先级的无限阻塞队列 |
3、饱和策略RejectedExecutionHandler
类型 | 说明 |
---|---|
AbortPolicy | 新的任务将被丢弃,并抛出异常(线程池默认饱和策略) |
DiscardPolicy | 新的任务将被丢弃,不会抛出异常 |
CallerRunsPolicy | 由调用者所在线程来运行当前任务 |
DiscardOldestPolicy | 丢弃队列最前面的任务,然后重新提交当前任务 |
… | 也可以根据应用场景需要实现RejectedExecutionHandler接口,实现自己的自定义策略,来满足如记录日志、持久化不能处理的任务等需求。 |
4、线程池的工作方式
当新提交一个任务时:
(1)、线程数量小于corePoolSize,即便线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
(2)、线程池中的线程数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。
(3)、线程池中的线程数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的线程数量小于maximumPoolSize,则创建新的线程来处理被添加的任务。
(4)、线程池中的线程数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的线程数量等于maximumPoolSize,那么通过handler所指定的策略来处理此任务。
处理任务的优先级为:
核心线程corePoolSize --> 任务队列workQueue --> 最大线程maximumPoolSize --> 使用handler饱和策略
4.1 核心线程数是如何保持不销毁的?
查看ThreadPoolExecutor.getTask()方法
/**
* Performs blocking or timed wait for a task, depending on
* current configuration settings, or returns null if this worker
* must exit because of any of:
* 1. There are more than maximumPoolSize workers (due to
* a call to setMaximumPoolSize).
* 2. The pool is stopped.
* 3. The pool is shutdown and the queue is empty.
* 4. This worker timed out waiting for a task, and timed-out
* workers are subject to termination (that is,
* {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
* both before and after the timed wait, and if the queue is
* non-empty, this worker is not the last thread in the pool.
*
* @return task, or null if the worker must exit, in which case
* workerCount is decremented
*/
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
// 无限死循环
for (;;) {
int c = ctl.get();
// 获取当前线程状态
int rs = runStateOf(c);
// 满足一定条件的情况下,检查任务队列是否为空?
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
// 获取线程池中线程数量
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//关键代码,以何种方式获取BlockingQueue<Runnable>中的对象,非阻塞 Or 阻塞?
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
是否保留核心线程,其中关键的代码是进入无限循环后,跳出循环的条件是怎么样的
timed条件为true的情况下,使用Queue的poll()方法,如果获取不到直接return null,此时会退出循环
timed条件为false的情况下,使用Queue的take()方法,如果获取不到则进入阻塞状态,直到获取到任务
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
- poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则等待time参数规定的时间,仍取不到时返回null
- take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,进入阻塞状态直到获取到新的队列对象为止
5、如何合理配置最大线程数
-
CPU密集型
定义:CPU密集型的意思就是该任务需要大量运算,没有阻塞,CPU一直全速运行。
CPU密集型任务只有在真正的多核CPU上才可能得到加速(通过多线程),单核多线程则意义不大
CPU密集型任务配置尽可能少的线程数。线程数 = CPU核数 + 1
-
IO密集型
定义:IO密集型,即该任务需要大量的IO,即大量的阻塞。
在单线程上运行IO密集型任务,会浪费大量的CPU运算能力在等待IO上。
所以IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要利用了被浪费掉的阻塞时间。 -
- 第一种配置方式:
由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程。线程数 = CPU核数 * 2
- 第一种配置方式:
-
- 第二种配置方式:
IO密集型时,大部分线程都阻塞,故需要多配置线程数。线程数 = CPU核数 / (1 – 阻塞系数(0.8~0.9之间))
例如: 8核 / (1 – 0.9) = 80个线程数
- 第二种配置方式:
6、代码演示
核心线程数:3,最大线程数:5,缓冲队列:10,饱和策略:AbortPolicy
package com.wzl.executor;
/**
* 工作类
*/
public class Worker implements Runnable {
int id;
public Worker(int id) {
this.id = id;
}
@Override
public void run() {
System.out.println("Index: " + id + " is running , Thread Name:" + Thread.currentThread().getName());
try {
Thread.sleep(6000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.wzl.executor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test {
private static final Logger log = LoggerFactory.getLogger(Test.class);
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
3,
5,
1 * 60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(10),
new ThreadPoolExecutor.AbortPolicy()//饱和策略,AbortPolicy:任务队列满后,新的任务将被丢弃,并抛出异常
);
for (int i = 0; i < 100; i++) {
try {
threadPoolExecutor.execute(new Worker(i));
} catch (Exception e) {
log.error("发生异常,当前index:" + i, e);
break;
}
}
threadPoolExecutor.shutdown();
}
}
运行结果 & 解答
- Index 0、1、2 交给corePoolSize,正在执行
- Index 3 ~ 12 进入缓冲队列(size == 10)进行等待
- Index 13、14 交给maximumPoolSize扩展的2个线程进行处理,正在执行
- Index 15 基于饱和策略,进行处理,这里是拒绝任务,抛出异常
- Index 3 ~ 12 等待的任务,在有空闲线程后进行处理