吐血整理:Java线程池源码分析(基于JDK1.8建议收藏)
文章目录
- 一、引言
- 二、线程池的参数介绍
- 1、ThreadPoolExecutor的UML图
- 三、线程池的使用
- 1、线程池的工作原理
- 2、线程池类型
- 2.1、newCachedThreadPool使用
- 2.2、newFixedThreadPool使用
- 2.3、newScheduledThreadPool使用
- 2.4、newSingleThreadExecutor使用
- 四、线程池的源码实现
- 1、线程池的状态
- 2、ThreadPoolExecutor重要的成员变量
- 3、线程池中任务提交
- 4、线程池中worker的执行
- 5、线程池获取任务
- 6、线程池关闭操作
- 6.1、 调用shutdown方法
- 6.2、 调用shutdownNow方法
- 7、awaitTermination操作
- 8、线程池容量的动态调整
- 五、线程池状态转换
- 六、总结
一、引言
一直以来都想写一个关于线程池源码的分析,但是这里面涉及的知识点很多没办法一次写完,因此一拖再多,现在实在不想再拖延下去了,总结一下线程池一些问题。如有不当的地方请各位同行及时指出,谢谢各位。
二、线程池的参数介绍
java.uitl.concurrent.ThreadPoolExecutor
类就是我们构造线程池的核心类,首先看一下这个类的构造。
1、ThreadPoolExecutor的UML图
从UML图中我们可以看出ThreadPoolExecutor
继承自抽象类AbstractExecutorService
实现ExecutorService
接口继承自Executor
接口 。
public class ThreadPoolExecutor extends AbstractExecutorService {public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);}public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);}public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), handler);}//最终调用的构造方法public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}
}
从上面的构造方法我们可以看出其他的构造方法都是调用最后一个构造方法完成初始化的。从上到下解释一下参数的含义:
int corePoolSize
:线程池核心线程数。根据这个英文的翻译我们可以看出这个值是线程池的核心数。默认情况下线程池创建的时候并没有创建任何线程,而是等到有任务的时候才创建线程去执行任务。除非调用了除非调用了prestartAllCoreThreads()
或者prestartCoreThread()
方法,从这2个方法是预创建线程的意思,即在没有任务到来之前就创建corePoolSize
个线程或者一个线程。当corePoolSize
线程来不及处理task任务的时候,任务就存到workQueue
阻塞队列。int maximumPoolSize
:线程池的最大线程数。它标识线程池最多容纳的线程数,如果核心线程已满,并且workQueue
阻塞队列也满了线程池将会扩容到maximumPoolSize
最大线程数处理task任务。long keepAliveTime
:表示线程空闲多长时间将会终止。默认情况下,只有当线程池数量大于corePoolSize
时,keepAliveTime
才会起作用,直到线程池中的线程数恢复到不大于corePoolSize
。但是如果调用了allowCoreThreadTimeOut(true)
方法,根据方法名我们也可以理解,如果调用这个方法就是允许核心线程超时,因此线程池线程数可减少到0。TimeUnit unit
:keepAliveTime的单位。
TimeUnit.DAYS; //天TimeUnit.HOURS; //小时TimeUnit.MINUTES; //分钟TimeUnit.SECONDS; //秒TimeUnit.MILLISECONDS; //毫秒TimeUnit.MICROSECONDS; //微妙TimeUnit.NANOSECONDS; //纳秒
BlockingQueue<Runnable> workQueue
:存放task的阻塞队列。当核心线程来不及处理的任务就会存到这个阻塞队列中。常用的有这几种选择:
ArrayBlockingQueue
:是有界队列,内部实现是将对象放到一个数组里。遵循FIFO先进先出原则。生产一般都使用有界队列配合线程池使用。LinkedBlockingQueue
:是无界队列,内部实现是 以链式结构对元素进行存储。遵循FIFO先进先出原则。使用该队列作为阻塞队列的时候要留意,如果没有设置上限,将会用Integer.MAX_VALUE 作为上限。SynchronousQueue
:同步移交队列,内部只够容纳单个元素。每个操作必须等到另个线程调用溢出操作,否则一直处于阻塞的状态。priorityBlockingQuene
:具有优先级的无界阻塞队列。所有插入到 priorityBlockingQuene的元素必须实现java.lang.Comparable
接口。因此该队列中元素的排序
就取决于你自己的Comparable
实现。注意:
(1) LinkedBlockingQueue比ArrayBlockingQueue在插入删除节点性能方面更优,因此有更大的吞吐量。但是二者在put(), take()任务的时均需要加锁。
(2) SynchronousQueue使用无锁算法,根据节点的状态判断执行,而不需要用到锁,其核心是Transfer.transfer()
ThreadFactory threadFactory
:线程工厂,主要用来创建worker线程。RejectedExecutionHandler handler
:处理任务的策略,有以下取值:
ThreadPoolExecutor.AbortPolicy
(默认策略):丢弃任务并抛RejectedExecutionException
异常。ThreadPoolExecutor.DiscardPolicy
:丢弃任务,但是不抛出异常。ThreadPoolExecutor.DiscardOldestPolicy
:丢弃队列最前面的任务,然后重新尝试执行当前任务。ThreadPoolExecutor.CallerRunsPolicy
:调用者所在的线程来处理该任务。
也可以根据应用使用场景来实现RejectedExecutionHandler
接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
三、线程池的使用
1、线程池的工作原理
简要说明一下线程池工作原理:
- 使用者提交新的task任务后线程池首先判断
核心线程池
是否已满,如果未满则重新创建新的worker线程。否则进入2。 - 判断
任务队列
是否已经满了,如果还没满将任务放入队列。否则进入3。 - 判断线程池的线程是否有空闲,如果没有,则重新创建worker线程。如果线程池已满,则执行
拒绝策略
。 - 如果worker线程处于空闲状态,则执行线程
超时销毁
。
线程池工作原理图如下:
线程池工作原理图:https://www.processon.com/view/link/5ef2d5545653bb2925b2ebeb
2、线程池类型
Java通过Executors(jdk1.5并发包)提供四种线程池,分别为:
newCachedThreadPool
:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。newFixedThreadPool
:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。newScheduledThreadPool
:创建一个定长线程池,支持定时及周期性任务执行。newSingleThreadExecutor
: 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
2.1、newCachedThreadPool使用
通过构造方法源码,我们可以看出这个线程池核心线程是0的无界线程池,也就是说空闲时间足够长核心线程可以变成0不消耗系统资源。
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}
测试代码如下:
public static void newCachedThreadPoolTest() throws InterruptedException {//创建可缓存的线程池ExecutorService threadPool = Executors.newCachedThreadPool();for (int i = 0; i < 10; i++) {final int finalI = i;threadPool.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + ",我是任务:" + finalI);}});}//休息5秒关闭线程池TimeUnit.SECONDS.sleep(5);threadPool.shutdown();}
打印结果:
pool-1-thread-1,我是任务:0
pool-1-thread-2,我是任务:1
pool-1-thread-3,我是任务:2
pool-1-thread-4,我是任务:3
pool-1-thread-6,我是任务:5
pool-1-thread-5,我是任务:4
pool-1-thread-7,我是任务:6
pool-1-thread-8,我是任务:7
pool-1-thread-9,我是任务:8
pool-1-thread-10,我是任务:9
2.2、newFixedThreadPool使用
通过构造方法我们可以看出这是个可定容量的线程池,大于核心线程的工作线程用完立即销毁。
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}
测试代码如下:
public static void newFixedThreadPoolTest() throws InterruptedException {//创建定长的线程池ExecutorService threadPool = Executors.newFixedThreadPool(5);for (int i = 0; i < 10; i++) {final int finalI = i;threadPool.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + ",我是任务:" + finalI);}});}//休息5秒关闭线程池TimeUnit.SECONDS.sleep(5);threadPool.shutdown();}
打印结果:
pool-1-thread-1,我是任务:0
pool-1-thread-3,我是任务:2
pool-1-thread-3,我是任务:6
pool-1-thread-5,我是任务:4
pool-1-thread-5,我是任务:8
pool-1-thread-1,我是任务:5
pool-1-thread-5,我是任务:9
pool-1-thread-3,我是任务:7
pool-1-thread-4,我是任务:3
pool-1-thread-2,我是任务:1
通过结果我们可以看出,线程池只有5个线程。
2.3、newScheduledThreadPool使用
通过构造方法我们可以看出这是个可定容量的线程池,他这里有schedule方法可以延迟线程池执行。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);}
测试代码:
public static void newScheduledThreadPoolTest() throws InterruptedException {//定时线程池ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);for (int i = 0; i < 10; i++) {final int finalI = i;threadPool.schedule(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + ",我是任务:" + finalI);}}, 2, TimeUnit.SECONDS);//等待2秒执行}//休息5秒关闭线程池TimeUnit.SECONDS.sleep(5);threadPool.shutdown();}
打印结果:
pool-1-thread-5,我是任务:4
pool-1-thread-1,我是任务:0
pool-1-thread-4,我是任务:3
pool-1-thread-3,我是任务:2
pool-1-thread-2,我是任务:1
pool-1-thread-3,我是任务:8
pool-1-thread-4,我是任务:7
pool-1-thread-1,我是任务:6
pool-1-thread-5,我是任务:5
pool-1-thread-2,我是任务:9
2.4、newSingleThreadExecutor使用
通过构造方法我们可以看出这是个线程池只有一个工作线程。
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}
测试代码
public static void newSingleThreadExecutorTest() throws InterruptedException {//创建定长的线程池ExecutorService threadPool = Executors.newSingleThreadExecutor();for (int i = 0; i < 10; i++) {final int finalI = i;threadPool.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + ",我是任务:" + finalI);}});}//休息5秒关闭线程池TimeUnit.SECONDS.sleep(5);threadPool.shutdown();}
打印日志:
pool-1-thread-1,我是任务:0
pool-1-thread-1,我是任务:1
pool-1-thread-1,我是任务:2
pool-1-thread-1,我是任务:3
pool-1-thread-1,我是任务:4
pool-1-thread-1,我是任务:5
pool-1-thread-1,我是任务:6
pool-1-thread-1,我是任务:7
pool-1-thread-1,我是任务:8
pool-1-thread-1,我是任务:9
四、线程池的源码实现
1、线程池的状态
主线程池控制状态ctl
是一个原子整数变量,包含两个部分:
runState
:线程的状态运行、关闭状态等,使用高3位表示(因为3位可以表示8个数,线程池状态只有5个足够了)workerCount
:有效线程数量,使用低29位表示,因此线程数的最多为2^29 -1
/*** 主线程池控制状态ctl是一个原子整数变量包含两个概念字段:* workerCount:有效线程数量* runState:线程的状态运行、关闭状态等。* <p/>* 状态转换的几种情况* RUNNING -> SHUTDOWN 执行了shutdown()方法或者间接的执行了finalize()方法* (RUNNING or SHUTDOWN) -> STOP 执行了shutdownNow()方法* SHUTDOWN -> TIDYING 当队列和线程池都是空的时候* TIDYING -> TERMINATED 当terminated()钩子方法完成时* <p/>* 等待awaitTermination()的线程将在状态达到TERMINATED时return*/private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));/*** 低29位,COUNT_BITS就是29*/private static final int COUNT_BITS = Integer.SIZE - 3;/*** 这个就是workerCount线程数量最大值,其实就是2^29 -1*/private static final int CAPACITY = (1 << COUNT_BITS) - 1;/*** 使用高3位就可以表示全部状态了,因为状态一共才5个,在计算机中数字都是补码表示,* 下面的移位操作都是按照补码来移位* <p/>* RUNNING: 接受新任务并处理排队的任务,并且也能处理阻塞队列中* 111.....0*/private static final int RUNNING = -1 << COUNT_BITS;/*** SHUTDOWN: 关闭状态,不再接受新提交的任务,但可以继续处理阻塞队列中已保存的任务* 000......0*/private static final int SHUTDOWN = 0 << COUNT_BITS;/*** STOP: 不接受新任务、不处理排队的任务,会中断正在处理任务的线程* 001......0*/private static final int STOP = 1 << COUNT_BITS;/*** TIDYING: 所有任务都已终止,workerCount为零* 010......0*/private static final int TIDYING = 2 << COUNT_BITS;/*** TERMINATED: terminated()钩子方法执行完成后进入该状态* 011......0*/private static final int TERMINATED = 3 << COUNT_BITS;
2、ThreadPoolExecutor重要的成员变量
线程池中以下成员变量比较重要,先要解释一下。
private volatile int corePoolSize; //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int maximumPoolSize; //线程池最大能容纳的线程数
private final BlockingQueue<Runnable> workQueue; //任务缓存队列,用来存放等待执行的任务
private volatile boolean allowCoreThreadTimeOut; //是否允许为核心线程设置存活时间
private volatile long keepAliveTime; //线程存活时间
private volatile int poolSize; //线程池中当前的线程数
private volatile RejectedExecutionHandler handler; //任务拒绝策略
private final ReentrantLock mainLock = new ReentrantLock(); //线程池内部独占锁,对线程池统计信息(线程池大小、runState等)的改变都使用这个锁
private final HashSet<Worker> workers = new HashSet<Worker>();//内部运行的Worker线程存放的地方,通过mainLock保证线程安全
private volatile ThreadFactory threadFactory; //线程工厂,用来创建线程
private int largestPoolSize; //用来记录线程池中曾经出现过的最大线程数,统计信息
private long completedTaskCount; //用来记录已经执行完毕的任务个数
3、线程池中任务提交
ThreadPoolExecutor实现的实际是一个生产者-消费者模型
,添加任务相当于生产者生产元素,workers线程工作集中的线程直接执行任务或者从任务队列中取出任务相当于消费者消费元素。提交任务方法execute
源码如下:
public void execute(Runnable command) {//(1)如果任务为null,则直接抛出NPE异常if (command == null)throw new NullPointerException();//(2)获取当前线程池的状态+线程个数变量的控制器ctlint c = ctl.get();//(3)当前线程个数是否小于corePoolSize,小于则添加一个新worker线程运行if (workerCountOf(c) < corePoolSize) {//(3.1)如果核心线程添加成功这返回if (addWorker(command, true))return;//(3.2)如果到这里代表核心线程添加失败了,则重新获取ctl的值c = ctl.get();}/*** 添加核心worker线程失败的几种场景:* 1.线程池为SHUTDOWN以上的状态* 2.当前线程池这种运行的worker的数量超过最大限制(2^29-1)* 3.当前线程池中运行的worker的数量超过corePoolSize*///(4)如果线程池处于RUNNING状态,则把任务添加到阻塞队列if (isRunning(c) && workQueue.offer(command)) {//(4.1)二次检查线程池是否处于RUNNING状态int recheck = ctl.get();//(4.2)如果当前线程池不是RUNNING状态则将任务从队列中移除并且执行拒绝策略if (!isRunning(recheck) && remove(command))reject(command);//(4.3)否则,如果当前的线程池为空,这添加一个worker线程else if (workerCountOf(recheck) == 0)addWorker(null, false);}//(5)如果队列满了,则新增线程,新增失败则执行拒绝策略else if (!addWorker(command, false))reject(command);}
其中的addWorker
方法源码如下:
private boolean addWorker(Runnable firstTask, boolean core) {retry:for (; ; ) {//获取当前线程池的状态+线程个数变量的控制器ctlint c = ctl.get();//获取线程池的状态int rs = runStateOf(c);/*** 提交任务过程中,如果此时线程池执行了shutdown操作,则不创建新线程* 第一个条件:rs >= SHUTDOWN,也就是此时已经执行了shutdown操作,可能的状态为有SHUTDOWN、STOP、TIDYING、TERMINATED* 第二个条件:!(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())* 等价于(!(rs == SHUTDOWN) || !(firstTask == null) || workQueue.isEmpty())只要有个一个为true就成立* 1.!(rs == SHUTDOWN) 为true表示rs!=SHUTDOWN的状态,也就是线程时处于SHUTDOWN、STOP、TIDYING、TERMINATED,不需要创建worker线程了* 2.(firstTask != null) 为true表示提交任务过程中,线程池刚好处于SHUTDOWN状态,不需要创建worker线程了* 3.workQueue.isEmpty() 为true表示任务队列已经空了,不需要创建worker线程了*///(6)检查队列是否是SHUTDOWN以上的状态if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))return false;//(7)循环CAS增加线程个数for (; ; ) {int wc = workerCountOf(c);//获取当前的工作线程个数//(7.1)如果线程个数超过限制则返回false,即添加工作线程失败if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))return false;//(7.2)CAS操作workCount+1,如果+1成功则跳出循环if (compareAndIncrementWorkerCount(c))break retry;c = ctl.get();//重新读取ctl的值//(7.3)CAS操作失败了,看线程池状态是否发生变化了,变化了则跳到外层循环重新获取线程池状态,否则内存循环重新获取CASif (runStateOf(c) != rs)continue retry;}}//(8)能到这里说明CAS成功了boolean workerStarted = false;//worker线程是否启动成功标志boolean workerAdded = false;//是否已经将worker线程添加到workers这个HashSet<Worker>集合中Worker w = null;try {//(8.1)创建worker线程w = new Worker(firstTask);//定义一个worker线程,这个woker线程绑定的是提交的task任务final Thread t = w.thread;if (t != null) {final ReentrantLock mainLock = this.mainLock;//(8.2)加独占锁,为了workers同步,因为可能多个线程调用了线程池的execute()方法mainLock.lock();try {//(8.3)重新检查线程池状态,为了避免在ThreadFactory失败或者在获取锁之前调用了shutdown方法int rs = runStateOf(ctl.get());if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {if (t.isAlive())//预先检查t是否可启动throw new IllegalThreadStateException();//(8.4)将worker线程添加到集合中workers.add(w);int s = workers.size();if (s > largestPoolSize)//largestPoolSize记录最大活跃线程数largestPoolSize = s;workerAdded = true;}} finally {mainLock.unlock();}//(8.5)添加成功则启动任务if (workerAdded) {t.start();workerStarted = true;}}} finally {//(8.6)如果启动失败了,则回退操作if (!workerStarted)addWorkerFailed(w);}return workerStarted;}
4、线程池中worker的执行
当我们完成了提交任务的过程的时候,下面一步就是worker线程是如何执行任务的。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {Worker(Runnable firstTask) {setState(-1); //在线程执行runWorker之前禁止被中断 this.firstTask = firstTask;//外部提交的任务this.thread = getThreadFactory().newThread(this);//真实的执行任务的线程}
}
从Worker类中我们可以看出它是实现了Runnable接口,并且是AQS的子类,那么我们可以推测出它能够进行并发的控制(lock、unlock)。runWorker
的实现如下:
final void runWorker(Worker w) {Thread wt = Thread.currentThread();//在woker的流程中执行worker.start()之后真实调用的方法Runnable task = w.firstTask;//获取当前worker携带的task任务w.firstTask = null;/*** 这里直接调用了unlock方法,但是我们并没有看到调用lock方法,说明unlock之前不一定需要lock*///(9)state设置成0,将占用锁的线程设置为null(第一次执行之前没有线程占用)w.unlock();boolean completedAbruptly = true;try {//(10.0)自旋。先执行自己携带的任务,然后从阻塞队列中获取一个任务直到无法获取任务while (task != null || (task = getTask()) != null) {//(10.1)将satte设置为1,设置占有锁的线程为自己w.lock();//(10.2)检查线程池状态,如果状态为STOP以上(STOP以上不执行任务),并且当前线程还未被中断则中断当前线程。//第二次检查状态是为了处理shutdownNow操作if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)))&& !wt.isInterrupted())wt.interrupt();try {//(10.3)这是⼀个钩⼦⽅法,留给需要的⼦类实现beforeExecute(wt, task);Throwable thrown = null;try {//(10.4)执⾏任务⽅法,若任务执⾏发⽣异常,当前worker不会再继续执⾏任务,线程销毁,后续会新增⼀个线程进⾏补偿task.run();} catch (RuntimeException x) {thrown = x;throw x;} catch (Error x) {thrown = x;throw x;} catch (Throwable x) {thrown = x;throw new Error(x);} finally {//(10.5)钩⼦⽅法,执⾏任务处理后逻辑,如异常处理afterExecute(task, thrown);}} finally {task = null;//(10.6)任务执⾏异常,也认为执行完毕,进行任务数量统计w.completedTasks++;w.unlock();}}/*** 执行到这里代表while循环结束了。worker线程正常结束了* 1.workQueue中没有任务了(poll超时,或者使用take时通过shutdown、shutdownNow中断了)* 2.worker线程执行时没有task出现异常,否则也会跳出循环*///(10.7)执行到这里代表非核心线程在keepAliveTime内无法获取任务而退出completedAbruptly = false;} finally {//(11)从上面可以看出如果实际业务(task任务)出现异常会导致当前worker终止//completedAbruptly此时为true代表worker突然完成,不是正常退出processWorkerExit(w, completedAbruptly);}}
其中processWorkerExit
方法的源码如下:
private void processWorkerExit(Worker w, boolean completedAbruptly) {if (completedAbruptly) //如果是task异常导致结束,workerCount数需要减1decrementWorkerCount();//(11.1)统计整个线程池完成的任务个数,并从works集合中删除当前workerfinal ReentrantLock mainLock = this.mainLock;mainLock.lock();try {completedTaskCount += w.completedTasks;workers.remove(w);} finally {mainLock.unlock();}//(11.2)尝试设置线程状态为TERMINATED//条件一:如果当前是SHUTDWON状态并且工作队列为空//或者//条件二:当前状态是STOP状态并且当前线程池里面没有活动线程tryTerminate();//(11.3)如果当前线程个数小于核心线程数,则新增worker线程int c = ctl.get();if (runStateLessThan(c, STOP)) {if (!completedAbruptly) {int min = allowCoreThreadTimeOut ? 0 : corePoolSize;if (min == 0 && !workQueue.isEmpty())min = 1;if (workerCountOf(c) >= min)return; // replacement not needed}addWorker(null, false);}}
5、线程池获取任务
worker线程会通过自旋方式一直循环获取task任务,先执行自己携带的任务,如果自己携带的任务为空则从阻塞队列中获取任务。
//(10)自旋。先执行自己携带的任务,然后从阻塞队列中获取一个任务直到无法获取任务while (task != null || (task = getTask()) != null){......}
其中,从阻塞队列中获取任务的getTask
方法源码如下:
private Runnable getTask() {boolean timedOut = false;//最近一次poll()超时了吗?//(10.0.1)自旋获取任务(因为是多线程环境)for (; ; ) {int c = ctl.get();int rs = runStateOf(c);/*** 1.线程池状态是SHUTDOWN状态并且任务队列是空* 2.线程池是STOP以上的状态* (10.0.2)满足以上两个条件则workerCount数-1,并且返回null从而保证获取任务的worker进行正常退出*/if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {decrementWorkerCount();return null;}int wc = workerCountOf(c);/*** 1.允许核心线程退出* 2.当前线程数量超过corePoolSize核心线程数* (10.0.3)这时获取任务的机制切换为poll(keepAliveTime)*/boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;/*** 1、线程数大于maximumPoolSize(什么时候会出现这种情况? 当maximumPoolSize初始设置为0或者其他线程通过set方法对其进行修改)* 2、线程数未超过maximumPoolSize但是timed为true(允许核心线程退出或者线程数量超过核心线程)并且上次获取任务超时(没获取到任务,我们推测本次依旧会超时)* 3、在满足条件1或者条件2的情况下进行check:运行线程数大于1或者任务队列没有任务* (10.0.4)满足以上条件执行worker线程数减1操作,并且返回null从而保证获取任务的worker进行正常退出*/if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {if (compareAndDecrementWorkerCount(c))return null;continue;}try {//(10.0.5)如果允许超时退出,则调用poll(keepAliveTime)获取任务,否则则通过tack()一直阻塞等待直到有任务提交到队列Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();if (r != null)return r;//(10.0.6)当等待超过keepAliveTime时间未获取到任务时,标记为true。在下次自旋时会进入销毁流程timedOut = true;} catch (InterruptedException retry) {//(10.0.7)什么时候会抛出异常?当调用shutdown或者shutdownNow方法触发worker内的Thread调用interrupt方法时会执行到此处timedOut = false;}}}
6、线程池关闭操作
线程池提供了两种关闭线程池的方法:
shutdown()
:调用后,不可以再 submit 新的 task,已经 submit 的将继续执行。shutdwonNow()
:调用后,试图停止当前正在执行的 task,并返回尚未执行的task的列表。
6.1、 调用shutdown方法
public void shutdown() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();//利用排它锁进行上锁,保证只有一个线程执行关闭流程try {//(12)权限检查checkShutdownAccess();//(13)内部通过自旋+CAS修改线程池状态为SHUTDOWNadvanceRunState(SHUTDOWN);//(14)遍历所有的worker,进行中断通知interruptIdleWorkers();onShutdown();//关闭线程池时调用的钩子函数} finally {mainLock.unlock();}//(15)进行最后的整理工作,尝试状态变为TERMINATEDtryTerminate();}//如果当前状态>=SHUTDOWN则直接返回,否则设置当前状态为SHUTDOWNprivate void advanceRunState(int targetState) {for (; ; ) {int c = ctl.get();if (runStateAtLeast(c, targetState) || ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))break;}}//设置所有空闲线程的中断标志private void interruptIdleWorkers(boolean onlyOne) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {for (Worker w : workers) {Thread t = w.thread;//如果工作线程没有被中断,并且没有正在运行则设置中断if (!t.isInterrupted() && w.tryLock()) {try {t.interrupt();} catch (SecurityException ignore) {} finally {w.unlock();}}if (onlyOne)break;}} finally {mainLock.unlock();}}//尝试终止线程池final void tryTerminate() {for (; ; ) {int c = ctl.get();/*** 1.处于RUNNING状态* 2.处于TIDYING、TERMINATED状态(已经终止过)* 3.处于SHUTDOWN状态但是workQueue不为空,还有任务未处理* (15.1)满足以上任何1种条件线程池不能被终止*/if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && !workQueue.isEmpty()))return;//当前线程池不能被为终止/*** 1.处于SHUTDOWN状态并且workQueue为空,或者STOP状态* 2.如果此时线程池还有线程(正在执行任务,正在等待任务),中断1个空闲的worker线程* (15.2)如果还有worker线程,只中断1个线程并返回*/if (workerCountOf(c) != 0) { //有资格终止interruptIdleWorkers(ONLY_ONE);return;}final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {//(15.3)当前已经没有运行态的线程了,将线程池状态设置为TIDYING,workerCount=0if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {try {//(15.4)钩子方法,待子类实现terminated();} finally {//(15.5)将线程池状态设置为TERMINATED,workerCount=0ctl.set(ctlOf(TERMINATED, 0));//(15.5)将线程池状态设置为TERMINATED后唤醒awaitTermination操作阻塞的线程termination.signalAll();}return;}} finally {mainLock.unlock();}// else retry on failed CAS}}
代码(15)判断如果当前线程池状态是SHUTDOWN状态并且工作队列为空
或者当前是STOP状态当前线程池里面没有活动线程
则设置线程池状态为TERMINATED
,如果设置为了TERMINATED
状态还需要调用条件变量termination的signalAll()
方法激活所有因为调用线程池的awaitTermination()
方法而被阻塞的线程。
6.2、 调用shutdownNow方法
调用shutdownNow()
后,线程池就不会在接受新的任务,并且丢弃工作队列里面里面的任务,正在执行的任务会被中断,该方法是立刻返回的,并不等待激活的任务执行完成在返回。返回值为这时候队列里面被丢弃的任务列表。代码如下:
public List<Runnable> shutdownNow() {List<Runnable> tasks;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {//(16)权限检查checkShutdownAccess();//(17)设置线程池状态为STOPadvanceRunState(STOP);//(18)中断所有线程interruptWorkers();//(19)移动任务队列到tasks中tasks = drainQueue();} finally {mainLock.unlock();}tryTerminate();return tasks;}//中断所有的worker线程,包含空闲线程和正在执行任务的线程private void interruptWorkers() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {for (Worker w : workers)w.interruptIfStarted();} finally {mainLock.unlock();}}
7、awaitTermination操作
线程池调用awaitTermination(long timeout, TimeUnit unit)
方法后,当前线程会被阻塞,直到线程池状态变为了TERMINATED
才返回,或者等待时间超时才返回,整个过程独占锁,代码如下:
public boolean awaitTermination(long timeout, TimeUnit unit)throws InterruptedException {long nanos = unit.toNanos(timeout);final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {for (; ; ) {if (runStateAtLeast(ctl.get(), TERMINATED))return true;if (nanos <= 0)return false;nanos = termination.awaitNanos(nanos);}} finally {mainLock.unlock();}}
8、线程池容量的动态调整
ThreadPoolExecutor提供了动态调整线程池容量大小的方法:
setCorePoolSize(int corePoolSize)
:设置核心池大小。public void setCorePoolSize(int corePoolSize) {if (corePoolSize < 0)throw new IllegalArgumentException();int delta = corePoolSize - this.corePoolSize;this.corePoolSize = corePoolSize;if (workerCountOf(ctl.get()) > corePoolSize)interruptIdleWorkers();else if (delta > 0) {// We don't really know how many new threads are "needed".// As a heuristic, prestart enough new workers (up to new// core size) to handle the current number of tasks in// queue, but stop if queue becomes empty while doing so.int k = Math.min(delta, workQueue.size());while (k-- > 0 && addWorker(null, true)) {if (workQueue.isEmpty())break;}}}
setMaximumPoolSize(int maximumPoolSize)
:设置线程池最大容量。public void setMaximumPoolSize(int maximumPoolSize) {if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)throw new IllegalArgumentException();this.maximumPoolSize = maximumPoolSize;if (workerCountOf(ctl.get()) > maximumPoolSize)interruptIdleWorkers();}
当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。
五、线程池状态转换
首先,总结一下线程池的状态(这里的状态值取移位之前的)。
运行状态 | 状态取值 | 状态描述 |
---|---|---|
RUNNING | -1 | 能接受新提交的任务,并且也能处理阻塞队列中的任务 |
SHUTDOWN | 0 | 关闭状态,不再接受新提交的任务,但可以继续处理阻塞队列中已保存的任务 |
STOP | 1 | 不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程 |
TIDYING | 2 | 所有的任务都终止了,workCount(有效线程数)为0 |
TERMINATED | 3 | 在terminated()钩子方法执行完后进入该状态 |
六、总结
如何选择线程池数量?
影响线程池大小的因素: CPU的数量、内存大小、 任务计算密集型还是IO密集型等。
牛人总结的线程池计算公式如下:
- NCPU = CPU的数量
- UCPU = 期望对CPU的使用率 0 ≤ UCPU ≤ 1
- W/C = 等待时间与计算时间的比率
如果希望处理器达到理想的使用率,那么线程池的最优大小为:
线程池大小=NCPU *UCPU(1+W/C)
一般需要根据任务的类型来配置线程池大小:
- 如果是CPU密集型任务,就需要尽量压榨CPU,
参考值可以设为 NCPU+1
- 如果是IO密集型任务,
参考值可以设置为2*NCPU
具体的设置还需要根据实际情况进行调整
,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。
吐血整理:Java线程池源码分析(基于JDK1.8建议收藏)相关推荐
- Java线程池 源码分析
1.个人总结及想法: (1)ThreadPoolExecutor的继承关系? ThreadPoolExecutor继承AbstractExectorService,AbstractExecutorSe ...
- 线程池源码分析-FutureTask
1 系列目录 线程池接口分析以及FutureTask设计实现 线程池源码分析-ThreadPoolExecutor 该系列打算从一个最简单的Executor执行器开始一步一步扩展到ThreadPool ...
- java 线程池 源码_java线程池源码分析
我们在关闭线程池的时候会使用shutdown()和shutdownNow(),那么问题来了: 这两个方法又什么区别呢? 他们背后的原理是什么呢? 线程池中线程超过了coresize后会怎么操作呢? 为 ...
- Java线程池源码解析及高质量代码案例
引言 本文为Java高级编程中的一些知识总结,其中第一章对Jdk 1.7.0_25中的多线程架构中的线程池ThreadPoolExecutor源码进行架构原理介绍以及源码解析.第二章则分析了几个违反J ...
- HashSet及LinkedHashSet源码分析(基于JDK1.6)
Java容器类的用途是"保存对象",分为两类:Map--存储"键值对"组成的对象:Collection--存储独立元素.Collection又可以分为List和 ...
- 线程池源码分析之ThreadPoolExecutor
前言 今天老吕给大家来分享下ThreadPoolExecutor 线程池的实现逻辑,大家伙认真看,一般人我不告诉他的. 线程池相关类图 JDK中线程池相关的类结构关系图 获取不同特性的线程池 在Exe ...
- 线程池之ScheduledThreadPoolExecutor线程池源码分析笔记
1.ScheduledThreadPoolExecutor 整体结构剖析. 1.1类图介绍 根据上面类图图可以看到Executor其实是一个工具类,里面提供了好多静态方法,根据用户选择返回不同的线程池 ...
- java定长池,java线程池源码学习
使用Executors创建线程池 Executor是一个工厂类,可以直接创建线程池,从最简单的定长线程池开始学习 public static ExecutorService newFixedThrea ...
- Zygote pre-fork线程池源码分析
前言 在Android Q上,google为了加快应用的启动速度.在zygote fork阶段,采用了线程池的方式,来加快fork的过程. 首先,如果让我们自己做,肯定会选择java的线程池模型,先创 ...
最新文章
- TileList自动滚动指定单元格,可视部分
- android studio资产目录,在Android Studio中设置单元测试的自定义资产目录
- sun building in shanghai
- ESP8266 如何修改默认上电校准方式?另外为什么 ESP8266 进⼊启动模式(2,7)并触发看⻔狗复位?
- mysql登录错误1045修改工具_mysql登录1045错误时 修改登录密码
- 程序编译时书写Makefile注意事项一例
- 更改oracle背景,Oracle 11gR2修改用户后导致系统HANG住
- 漫画 | 中断引发的面试教训
- JSON解析方式 gson
- 用svn进行多人合作开发
- 2022电大国家开放大学网上形考任务-劳动与社会保障法非免费(非答案)
- javplayer 使用教程_童装裁剪之连衣裙打版教程 有图纸
- mysql导入文本或excel文件
- 特征重要性判断(一)----决策树
- 我是如何拿到:百度 腾讯 头条 美团 度小满等互联网offer的?
- 在家访问办公室电脑能实现吗?远程控制办公室电脑的软件推荐
- linux归档和压缩的区别,文件的归档和压缩
- Bullet physics 引擎的官方文档翻译
- 自学mysql还是sql好_都说自学SQL数据库难,是真的吗?
- 未来计算机发展的三大趋势,报告:5G未来发展会有三大趋势 中国有望成为全球最大的AI市场...