目录
ThreadPoolExecutor源码分析
一、ThreadPoolExecutor应用方式
为什么要使用线程池?
jdk中已经提供了Executors,提供了很多封装好的线程池。为什么还要自己创建使用线程池?
1.1 Executor创建线程的四种方式
1.1.1 newCachedThreadPool
创建一个可缓存的无界线程池,如果线程池长度超过处理需要,可灵活回收空线程,若无可回收,则新建线程。当线程池中的线程空闲时间超过60s,则会自动回收该线程,当任务超过线程池的线程数则创建新的线程,线程池的大小上限为Integer.MAX_VALUE,可看作无限大。
/**
* 可缓存无界线程池测试
* 当线程池中的线程空闲时间超过60s则会自动回收该线程,核心线程数为0
* 当任务超过线程池的线程数则创建新线程。线程池的大小上限为Integer.MAX_VALUE,
* 可看做是无限大。
*/
@Test
public void cacheThreadPoolTest() {
// 创建可缓存的无界线程池,可以指定线程工厂,也可以不指定线程工厂
ExecutorService executorService = Executors.newCachedThreadPool(new testThreadPoolFactory("cachedThread"));
for (int i = 0; i < 10; i++) {
executorService.submit(() -> {
print("cachedThreadPool");
System.out.println(Thread.currentThread().getName());
}
);
}
}
1.1.2 newFixedThreadPool
创建一个指定大小的线程池,可控制线程的最大并发数,超出的线程会在LinkedBlockingQueue阻塞队列中等待
/**
* 创建固定线程数量的线程池测试
* 创建一个固定大小的线程池,该方法可指定线程池的固定大小,对于超出的线程会在LinkedBlockingQueue队列中等待
* 核心线程数可以指定,线程空闲时间为0
*/
@Test
public void fixedThreadPoolTest() {
ExecutorService executorService = Executors.newFixedThreadPool(5, new testThreadPoolFactory("fixedThreadPool"));
for (int i = 0; i < 10; i++) {
executorService.submit(() -> {
print("fixedThreadPool");
System.out.println(Thread.currentThread().getName());
}
);
}
}
1.1.3 newScheduledThreadPool
创建一个定长的线程池,可以指定线程池核心线程数,支持定时及周期性任务的执行
/**
* 创建定时周期执行的线程池测试
*
* schedule(Runnable command, long delay, TimeUnit unit),延迟一定时间后执行Runnable任务;
* schedule(Callable callable, long delay, TimeUnit unit),延迟一定时间后执行Callable任务;
* scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit),延迟一定时间后,以间隔period时间的频率周期性地执行任务;
* scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit),与scheduleAtFixedRate()方法很类似,
* 但是不同的是scheduleWithFixedDelay()方法的周期时间间隔是以上一个任务执行结束到下一个任务开始执行的间隔,而scheduleAtFixedRate()方法的周期时间间隔是以上一个任务开始执行到下一个任务开始执行的间隔,
* 也就是这一些任务系列的触发时间都是可预知的。
* ScheduledExecutorService功能强大,对于定时执行的任务,建议多采用该方法。
*/
@Test
public void scheduleThreadPoolTest() {
// 创建指定核心线程数,但最大线程数是Integer.MAX_VALUE的可定时执行或周期执行任务的线程池
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5, new testThreadPoolFactory("scheduledThread"));
// 定时执行一次的任务,延迟1s后执行
executorService.schedule(new Runnable() {
@Override
public void run() {
print("scheduleThreadPool");
System.out.println(Thread.currentThread().getName() + ", delay 1s");
}
}, 1, TimeUnit.SECONDS);
// 周期性地执行任务,延迟2s后,每3s一次地周期性执行任务
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ", every 3s");
}
}, 2, 3, TimeUnit.SECONDS);
executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
long start = new Date().getTime();
System.out.println("scheduleWithFixedDelay 开始执行时间:" +
DateFormat.getTimeInstance().format(new Date()));
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = new Date().getTime();
System.out.println("scheduleWithFixedDelay执行花费时间=" + (end - start) / 1000 + "m");
System.out.println("scheduleWithFixedDelay执行完成时间:"
+ DateFormat.getTimeInstance().format(new Date()));
System.out.println("======================================");
}
}, 1, 2, TimeUnit.SECONDS);
}
1.1.4 newSingleThreadExecutor
创建一个单线程化的线程池,它只有一个线程,用仅有的一个线程来执行任务,保证所有的任务按照指定顺序(FIFO,LIFO,优先级)执行,所有的任务都保存在队列LinkedBlockingQueue中,等待唯一的单线程来执行任务。
/**
* 创建只有一个线程的线程池测试
* 该方法无参数,所有任务都保存队列LinkedBlockingQueue中,核心线程数为1,线程空闲时间为0
* 等待唯一的单线程来执行任务,并保证所有任务按照指定顺序(FIFO或优先级)执行
*/
@Test
public void singleThreadPoolTest() {
// 创建仅有单个线程的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor(new testThreadPoolFactory("singleThreadPool"));
for (int i = 0; i < 10; i++) {
executorService.submit(() -> {
print("singleThreadPool");
System.out.println(Thread.currentThread().getName());
}
);
}
}
1.1.5 四种方法的比较
其他参数都相同,其中线程工厂的默认类为DefaultThreadFactory,线程饱和的默认策略为ThreadPoolExecutor.AbortPolicy。
1.2 自定义方式创建线程池
线程池的使用很简单:
-
只需要构建好ThreadPoolExecutor对象即可,传入指定的参数
-
在执行Runnable任务的时候,可以直接调用execute方法执行
-
在执行Callable任务的时候,需要有返回结果,直接调用submit方法执行
二、ThreadPoolExecutor核心参数
-
corePoolSize:核心线程数
-
maximumPoolSize:最大线程数。当核心线程数已经满了,且工作队列workQueue也满了之后。判断线程池中的线程数(这里面指的是核心线程数,并不包括阻塞队列中的线程数)有没有达到maximumPoolSize,没有的话就新建一个工作线程来执行这个任务。
-
keepAliveTime:非核心线程的空闲时间
-
unit:就是keepAliveTime的单位
-
workQueue:工作队列(阻塞队列),当核心线程没有多余或者说空闲的时候,就把任务放在该任务队列中去排队。BlockQueue的实现类主要有下面这几种,用的最多的就是ArrayBlockingQueue和LinkedBlockingQueue。前者是指定最多有多少个队列可以排。后面一个是任意排。
-
ThreadFactory:线程工厂。通过这个线程工厂去创建这些线程。
-
handler:拒绝策略。就是上面的核心线程数、阻塞队列和最大线程数都非空闲的话就会走这个拒绝策略。
对于AbortPolicy:默认的拒绝策略。直接抛出异常。
CallerRunsPolicy:由主线程执行,会对主线程造成影响。
DiscardOldestPolicy:将阻塞队列中排在最前面的任务弹出(队列是先进先出),再将新的任务加入阻塞队列。
DiscardPolicy:什么事情都不做。不处理。
三、ThreadPoolExecutor执行流程
业务线程提交任务到线程池之后,任务的处理流程。
-
先判断工作线程数是否小于核心线程数,满足的话,创建核心线程数并执行任务。
-
不满足上面的条件的话,看阻塞队列能不能存放该线程,能的话就存入阻塞队列。
-
不满足上面的条件的话,看工作线程数是否已经大于最大线程数。不大于的话,就创建非核心线程,并执行任务。
-
如果,工作线程数大于非核心线程数的话,就走拒绝策略
3.1 具体例子
package start;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author xuwei
* @create 2023-03-20 19:48
*/
public class Test {
public static void main(String[] args) throws InterruptedException{
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1,
2,
500,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("测试1");
return t;
}
},
new ThreadPoolExecutor.AbortPolicy());
//执行
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("嘿嘿!!");
}
});
}
}
四、ThreadPoolExecutor状态
4.1 线程池中的核心属性ctl
ctl本质就是一个int类型的数值
//ctl的本质就是一个int类型的数值
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//Integer.SIZE = 32 , COUNT_BITS = 29
//ctl表述了两个状态:
//1.表示线程池当前的状态(高三位)
//2.标识线程池当前的工作线程个数(底29位)
private static final int COUNT_BITS = Integer.SIZE - 3;
//工作线程的最大数量
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 高3位,线程池的5中状态
//111
private static final int RUNNING = -1 << COUNT_BITS;
//000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//001
private static final int STOP = 1 << COUNT_BITS;
//010
private static final int TIDYING = 2 << COUNT_BITS;
//011
private static final int TERMINATED = 3 << COUNT_BITS;
//计算出当前线程池的状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//计算当前线程池中的工作线程个数
private static int workerCountOf(int c) { return c & CAPACITY; }
4.2线程池的状态变换
-
RUNNING:正常接收任务,可以处理任务、处理工作队列中的任务
-
SHUTDOWN:由running状态调用shutdown()方法进入该状态。这个状态不会接收线程任务,但是会处理任务,以及处理工作队列中的任务
-
STOP:由running状态调用shutdownNow()方法进入该状态。这个状态不接受线程任务,并中断正在处理任务的线程,不处理工作队列中的任务。
-
TIDYING:可由shutdown状态执行tryTerminate()方法进入这个状态。但是有前提,就是工作线程数为0,并且工作队列为空。也可以由stop状态调用tryTerminate()方法进入这个状态。前提是工作线程数为0。所以TINYING也被称为过渡状态,到这个状态就表示这个线程池马上就干掉了。
-
TERMINATED:TINYING状态调用terminated()方法进入TERMINATED状态,这时候线程池就被销毁了。
五、executor方法
public void execute(Runnable command) {
//进行非空判断
if (command == null)
throw new NullPointerException();
//获取ctl属性
int c = ctl.get();
//工作线程的个数 是否小于 核心线程数
if (workerCountOf(c) < corePoolSize) {
//通过add方法,添加一个核心线程去执行command任务
if (addWorker(command, true))
//添加核心线程数成功,返回true,直接return结束
return;
//如果在并发情况下,添加核心线程数失败的情况,需要重新获取一次ctl
c = ctl.get();
}
//创建核心线程池失败的情况
//判断当前线程池状态是否是RUNNING
//如果是RUNNING,执行offer方法将任务添加到工作队列中
if (isRunning(c) && workQueue.offer(command)) {
//添加任务到工作队列成功
//再次重新获取ctl
int recheck = ctl.get();
//判断线程池是否是RUNNING状态,如果不是RUNNNIG状态,需要将任务从工作队列移除
if (! isRunning(recheck) && remove(command))
//执行拒绝策略
reject(command);
//判断工作线程是否为0
else if (workerCountOf(recheck) == 0)
//工作线程数为0,但是工作队列中有任务在排队
//添加一个空任务非核心线程,为了处理在工作队列中排队的任务
addWorker(null, false);
}
//添加工作队列失败,添加非核心线程去执行当前任务
else if (!addWorker(command, false))
//添加非核心线程失败,执行reject拒绝策略
reject(command);
}