Java并发之ThreadPoolExecutor源码解析(二)
ThreadPoolExecutor
ThreadPoolExecutor是ExecutorService的一种实现,可以用若干已经池化的线程执行被提交的任务。使用线程池可以帮助我们限定和整合程序资源,尽可能避免创建新的线程来执行任务从而降低任务调用的开销,在执行大量异步任务的时候反而能获得更好的性能。此外,ThreadPoolExecutor还会维护一些统计信息,比如已完成的任务数量。
juc包的作者Doug Lea推荐程序员尽量使用更为便利的Executors类的工厂方法来配置线程池:
- Executors.newCachedThreadPool():创建一个线程池,可以根据线程池的需要来创建任务。这个线程池比较适用执行周期较短且量大的异步任务,在调用execute(...)方法时如果线程池中存在闲置的线程,将复用闲置线程,否则创建一个新线程执行任务。如果线程的闲置时间超过60s,线程将被终止并从线程池内移除。因此,该线程池即便空闲时间再长,也不会有资源的消耗。
- Executors.newFixedThreadPool(int nThreads):创建一个线程池,nThreads为池内线程的数量,池内最多同时有nThreads个线程并行处理任务,如果有新的任务提交到线程池,会先暂存在线程池中的无边界任务队列进行等待,直到有线程可用。如果有线程在执行期间因为错误提前终止,线程池将启动一个新的线程代替原先的线程继续处理任务队列中处于等待的任务。除非显式调用shutdown(),否则线程池中的线程将一直存在。
- Executors.newSingleThreadExecutor():创建一个Executor,该Executor使用一个工作线程处理任务。如果线程在执行期间因为错误而终止,将启动一个新的线程代替原先的线程继续处理无边界任务队列中处于等待的任务。队列中的任务是按顺序执行,任何时刻都不会有多个任务处于活跃状态。与newFixedThreadPool(1)不同,newFixedThreadPool(int nThreads)生成的线程池,可以强转为ThreadPoolExecutor类型,再调用setCorePoolSize(int corePoolSize)方法设置核心线程数,而newSingleThreadExecutor()的实现类为FinalizableDelegatedExecutorService,无法直接设置核心线程数。
上面三种是较为常见的配置线程池的工厂方法,如果有需要根据业务场景特殊配置线程池的,请看下面的参数:
核心线程数和最大线程数
ThreadPoolExecutor将根据corePoolSize(核心线程数)和maximumPoolSize(最大线程数)设置的边界自动调整线程池内工作线程的数量(通过getPoolSize()),corePoolSize可以通过getCorePoolSize()、setCorePoolSize(int corePoolSize)获取和设置核心线程数,maximumPoolSize可以通过getMaximumPoolSize()、setMaximumPoolSize(int maximumPoolSize)获取和设置最大线程数。当有新的任务提交时,如果工作线程少于核心线程数,将会创建一个新线程来执行该任务,即便其他工作线程处于闲置状态。如果工作线程多于corePoolSize但少于maximumPoolSize,则当任务队列满的时候才会创建新线程。如果corePoolSize和maximumPoolSize数值一样,则创建一个固定大小的线程池;如果将maximumPoolSize设置为Integer.MAX_VALUE,则线程池可以容纳任意数量的并发任务。
按需构造
核心线程只有当有新任务到达时才会创建,但我们可以重写prestartCoreThread() 或者prestartAllCoreThreads()来预先启动核心线程。如果在构造一个线程池时,传入的任务队列已经存在任务,则需要线程池初始化完毕后,预先启动线程。
创建新线程
使用ThreadFactory(线程工厂)创建新线程。如果没有特别指定,则使用Executors.defaultThreadFactory()作为默认的线程工厂,该线程工厂所创建的线程都位于相同的线程组(ThreadGroup)中,线程的优先级都是NORM_PRIORITY,线程守护状态都为false。通过提供不同线程工厂的实现,你可以修改线程名、线程组、线程优先级和守护状态等等。
活跃时间
如果线程池中的线程数超过核心线程数,多出的线程如果空闲时间超出keepAliveTime(活跃时间)将会终止,回收不再活跃的线程。当有需要时,新的线程会重新创建。可以通过setKeepAliveTime(long time, TimeUnit unit)动态设置活跃时间。如果time设置为Long.MAX_VALUE,unit设置为TimeUnit.NANOSECONDS,那么多余的空闲线程将不会在关闭线程池之前回收。如果调用allowCoreThreadTimeOut(boolean value)传入的value为true,那么keepAliveTime将适用于核心线程,如果allowCoreThreadTimeOut为true且keepAliveTime不为0,核心线程的空闲时间超出活跃时间,核心线程也会被回收。
队列
阻塞队列(BlockingQueue)允许在获取元素时陷入等待,直到有元素加入到队列中。调用阻塞队列方法时,有些方法不一定马上返回,可能会在未来某个时刻达成某些条件时返回。阻塞队列的方法伴随四种形式:
- 抛出异常。
- 返回特殊值,null或者false,具体视操作而定。
- 调用线程无限期陷入阻塞直到某些条件达成。
- 限定阻塞时长。
抛异常 | 特殊值 | 阻塞 | 超时 | |
插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
移除 | remove() | poll() | take() | poll(time, unit) |
检查(获取但不移除队列头部元素) | element() | peek() |
阻塞队列不接受null元素,如果调用add、put、offer尝试添加一个null元素,将会抛出NullPointerException异常,当调用poll操作失败时也会返回null。阻塞队列可能有容量限制,无论何时都不能向队列添加超过剩余容量的元素,否则只能调用put方法陷入阻塞,直到有剩余的空间可以容纳元素。如果对队列的容纳空间没有限制,则剩余容量返回Integer.MAX_VALUE。阻塞队列的实现一般用于生产者-消费者队列的场景,此外阻塞队列还实现了Collection接口,因此,队列还可以使用remove(x)来移除元素。
阻塞队列是线程安全的,所有排队方法的实现都是用内部锁或者其他并发控制手段来实现原子性的。然而,除非是特殊规定,否则大部分集合操作,如:addAll、containsAll、retainAll 、removeAll不一定要保证原子性。因此,可能出现在调用addAll(c)时,只添加c中一部分的元素就抛出异常。阻塞队列本质上并不支持关闭的操作,如:close或shutdown,当有需要让队列不再接受新元素。如果有这种需要或者特性更倾向于以来队列的实现。一种常见的策略是生产者往队列插入具有特殊标识的对象,当消费者使用对象时,会对特殊标识进行解释。
注意,阻塞队列允许多个生产者和消费者同时使用,如下:
class Producer implements Runnable {
private final BlockingQueue queue;
Producer(BlockingQueue q) { queue = q; }
public void run() {
try {
while (true) { queue.put(produce()); }
} catch (InterruptedException ex) { ... handle ...}
}
Object produce() { ... }
} class Consumer implements Runnable {
private final BlockingQueue queue;
Consumer(BlockingQueue q) { queue = q; }
public void run() {
try {
while (true) { consume(queue.take()); }
} catch (InterruptedException ex) { ... handle ...}
}
void consume(Object x) { ... }
} class Setup {
void main() {
BlockingQueue q = new SomeQueueImplementation();
Producer p = new Producer(q);
Consumer c1 = new Consumer(q);
Consumer c2 = new Consumer(q);
new Thread(p).start();
new Thread(c1).start();
new Thread(c2).start();
}
}
public interface BlockingQueue<E> extends Queue<E> {
/**
* 在不超过队列容量的情况下插入一个元素将返回true,,如果队列没有多余的空间抛出
* IllegalStateException异常,当使用容量受限的队列时最好使用offer。
*
* @param e 待添加进队列的元素
* @return 返回true代表元素加入队列成功
*/
boolean add(E e); /**
* 相比add(E)如果队列满时插入元素不报错,只是返回false。
*
* @param e 待添加进队列的元素
* @return 返回true代表元素加入队列成功,队列满时无法插入返回false
*/
boolean offer(E e); /**
* 将一个元素插入到队列,如果有必要会等待队列有多余空间可以插入。如果调用
* put(E)的线程被中断,将抛出中断异常InterruptedException
*
* @param e 待添加进队列的元素
*/
void put(E e) throws InterruptedException; /**
* 相比offer(E)多了插入元素时陷入等待,如果等待期间队列依旧
* 没有多余的空间容纳元素,则返回false,如果等待期间能插入则返回true。
* 如果等待期间线程被中断,则抛出中断异常InterruptedException。
*
* @param e 待添加进队列的元素
* @param timeout 等待时长
* @param unit 等待时长单位
*/
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException; /**
* 取出并删除队列的头部元素,如果队列为空,则会陷入等待,直到队列有新的元素加入,
* 如果等待期间线程被中断,将抛出中断异常
*
* @return 对头元素
*/
E take() throws InterruptedException; /**
* 相比take()多了一个等待时长,如果队列本身有元素,或者队列原先有空但等待期间有元素
* 加入则返回头部元素,否则队列为空且等待期间没有元素加入,则返回null。如果等待期间调用线程
* 被中断,则抛出InterruptedException异常。
*/
E poll(long timeout, TimeUnit unit)
throws InterruptedException; /**
* 返回队列理想状况下可无阻塞容纳元素的容量。注意:我们不能通过此方法判断元素是否插入成功,
* 因为可能存在别的线程插入或删除队列中的元素。
*/
int remainingCapacity(); /**
* 从队列中移除指定的元素,如果队列中存在一个或多个相同的元素,即:o.equal(e),则删除并返回true。
*/
boolean remove(Object o); /**
* 如果队列存在一个或多个相同的元素,即:o.equal(e),则返回true。
*/
boolean contains(Object o); /**
* 删除此队列中所有可用元素,并将它们移动到给定的集合c中。当我们把元素从原队列取出时添加到集合c时,
* 可能出现异常导致元素既不在原队列,也不在集合中。队列同样实现了Collection接口,如果将原队列当做
* 参数传入将抛出IllegalArgumentException异常
* @param c 将队列元素传输到给定的集合c。
* @return 加入到集合c中元素的数量。
*/
int drainTo(Collection<? super E> c); /**
* 最多将maxElements个元素从队列传输到给定集合,其他和drainTo(Collection<? super E> c)一样。
*/
int drainTo(Collection<? super E> c, int maxElements);
}
任何阻塞队列都可以用来获取和保存任务,如何使用队列视当前线程池的大小而定:
- 如果工作线程的数量小于corePoolSize,那么Executor更倾向于添加新线程执行而非让任务排队。
- 如果工作线程的数量大于等于corePoolSize,那么Executor更倾向于把任务添加到队列等待执行,而非创建新线程。
- 如果队列已满,且线程池内的线程数小于maximumPoolSize,Executor会创建一个新线程来执行任务,否则任务会被拒绝。
通常有三种排队策略:
- 同步队列(SynchronousQueue):如果有线程从同步队列获取任务,则移交给线程,否则持有任务,如果有新的任务尝试入队将返回失败,可以根据入队结果判断是否要构造一个新线程。同步队列可以避免当处理多个请求时内部依赖出现锁定,直接交接任务要求对maximumPoolSize这一参数不做限制,即maximumPoolSize为Integer.MAX_VALUE,避免线程池拒绝提交任务。但如果线程池处理任务的速度不够快,可能出现线程无限增长。
- 无界队列(LinkedBlockingQueue):如果线程池核心工作线程都在执行任务时,新提交的任务将在队列中等待。如果任务提交速度过快而执行任务的速度又慢,将导致队列中的任务无限增长。
- 有界队列(ArrayBlockingQueue):当与有限的maximumPoolSize配合使用时,有界队列可以防止资源耗尽,但如何设定有界队列的大小是一个很难的问题。如果队列过大而maximumPoolSize过小,可以减少CPU的使用、操作系统资源和上下文切换的开销,这有可能导致低吞吐量。如果队列过小而maximumPoolSize过大,这会使得CPU十分繁忙,甚至出现巨大的调度开销,同样也会降低吞吐量。
拒绝任务
如果线程池关闭后有新任务提交、或者在任务队列已满的情况下,线程池到达最大线程数且所有线程都在执行任务,将调用RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor)拒绝任务。默认提供四种预定义拒绝策略:
- ThreadPoolExecutor.AbortPolicy:默认情况下使用的策略,抛出RejectedExecutionException异常。
- ThreadPoolExecutor.CallerRunsPolicy:使用调用线程执行任务,这种机制可以降低任务提交的速度。
- ThreadPoolExecutor.DiscardPolicy:将无法入队也不能执行的任务直接丢弃。
- ThreadPoolExecutor.DiscardOldestPolicy:如果线程池未关闭,则获取并丢弃队列的头部元素,再尝试用线程池执行任务,这一策略可能会失败,如果失败则重复之前的步骤。
除了上述四种步骤,我们也可以自定义拒绝策略。
钩子函数
ThreadPoolExecutor提供了可重写函数beforeExecute(java.lang.Thread, java.lang.Runnable)、afterExecute(java.lang.Runnable, java.lang.Throwable),分别允许我们在执行任务前和执行任务后做一些操作,这些方法可以控制执行环境,例如:初始化ThreadLocals、收集统计信息、添加日志等等。此外,也可以重写terminated()方法,当线程池完全终止后会调用此方法。
如果钩子函数或者回调函数抛出异常,工作线程可能会终止。
队列维护
ThreadPoolExecutor提供了getQueue()允许外部获取队列进行监控和调试,但不管出于什么目的尽量少使用此方法。此外ThreadPoolExecutor还提供了remove(java.lang.Runnable) 和purge()用于删除任务,purge()可以取消大量处于排队等待的任务。
销毁
当一个线程池不再有引用指向,且线程池内没有存活线程将会自动关闭。如果你期望一个未手动调用shutdown()方法的线程池会被回收,你要设置合理的线程存活时间(keep-alive times)、设置核心线程数为0,或者设置allowCoreThreadTimeOut为true,当核心线程空闲时间超过存活时间将被回收,当线程池没有引用指向,且无存活线程,就会被自动关闭并回收。
源码解析
ThreadPoolExecutor的ctl变量类型为AtomicInteger,这个数值有32位,包含两个部分:
- runState(运行状态):线程池是否处于运行中、是否已关闭等等。
- workerCount(工作线程数):线程池当前有多少个存活(忙碌或空闲)的线程。
为了将运行状态和工作线程数放在一个int字段,我们划分前3位存储运行状态,后29位存储存活线程数量(2^29)-1(约5亿)。未来有可能调整ctl为AtomicLong类型,这可能需要调整移位和掩码,但如果使用AtomicInteger,ThreadPoolExecutor的代码会更简单也更高效一些。
workerCount是线程池中还存活的线程数,该值有时候可能会短暂不同于池内实际的存活线程数。当需要增加工作线程时,会先用CAS的方式对workerCount+1,然后才向ThreadFactory申请创建一个线程。
runState为线程池提供了生命周期控制,有以下几种状态:
- RUNNING:允许接受新任务和处理队列中任务。
- SHUTDOWN:不接受新任务,但处理队列中任务。
- STOP:不接受新任务,不处理队列中任务,同时尝试中断正在执行的任务。
- TIDYING:所有任务都终止,workerCount为0,线程池状态过度到TIDYING,将要执行terminated()钩子函数。
- TERMINATED:terminated()函数执行完毕。
下面,我们来看看线程池状态的转换:
- RUNNING->SHUTDOWN:调用shutdown()。
- (RUNNING or SHUTDOWN)->STOP:调用shutdownNow()。
- SHUTDOWN->TIDYING:当线程池不再有存活线程且队列为空。
- STOP->TIDYING:当线程池不再有存活线程。
- TIDYING->TERMINATED:调用terminated()。
检测线程池的状态从SHUTDOWN过度到TIDYING并非易事,因为在SHUTDOWN状态下,队列可能从非空变为空,即仍然有存活的线程处理队列中的任务。只有workerCount为0且队列为空,才能结束线程池。
public class ThreadPoolExecutor extends AbstractExecutorService {
/**
* 类型为AtomicInteger的ctl可以保证线程安全,该数值分两个部分:
* 前3位为runState代表线程池当前的状态:RUNNING~TERMINATED,
* 后29位为workerCount代表线程池内存活线程数量。
*/
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
/**
* Integer.SIZE=32,COUNT_BITS=32-3=29,用COUNT_BITS来划分
* runState和workerCount。
*/
private static final int COUNT_BITS = Integer.SIZE - 3;
/**
* 1 << COUNT_BITS = 0010 0000 0000 0000 0000 0000 0000 0000
* COUNT_MASK = (1 << COUNT_BITS) - 1 = 0001 1111 1111 1111 1111 1111 1111 1111
* ~COUNT_MASK = 1110 0000 0000 0000 0000 0000 0000 0000
* COUNT_MASK可以帮助我们计算线程池当前的runState和workerCount。
* 假设ctl的值为:0000 0000 0000 0000 0000 0000 0000 0011
* 调用runStateOf(ctl.get()),将做ctl.get() & ~COUNT_MASK运算:
* 0000 0000 0000 0000 0000 0000 0000 0011
* & 1110 0000 0000 0000 0000 0000 0000 0000
* = 0000 0000 0000 0000 0000 0000 0000 0000
* 由此我们可以得到,线程池当前状态为0,即SHUTDOWN。
* 调用workerCountOf(ctl.get()),将做ctl.get() & COUNT_MASK运算:
* 0000 0000 0000 0000 0000 0000 0000 0011
* & 0001 1111 1111 1111 1111 1111 1111 1111
* = 0000 0000 0000 0000 0000 0000 0000 0011
* 由此我们可以得到,线程池还有3个存活的线程。
*/
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1; /**
* -1的二进制表示为:1111 1111 1111 1111 1111 1111 1111 1111,
* 左移29位为:1110 0000 0000 0000 0000 0000 0000 0000
*/
private static final int RUNNING = -1 << COUNT_BITS;
/**
* 0的二进制表示为:0000 0000 0000 0000 0000 0000 0000 0000,
* 左移29位和原先没有变化。
*/
private static final int SHUTDOWN = 0 << COUNT_BITS;
/**
* 0的二进制表示为:0000 0000 0000 0000 0000 0000 0000 0001,
* 左移29位为:0010 0000 0000 0000 0000 0000 0000 0000
*/
private static final int STOP = 1 << COUNT_BITS;
/**
* 2的二进制表示为:0000 0000 0000 0000 0000 0000 0000 0010,
* 左移29位为:0100 0000 0000 0000 0000 0000 0000 0000
*/
private static final int TIDYING = 2 << COUNT_BITS;
/**
* 3的二进制表示为:0000 0000 0000 0000 0000 0000 0000 0011,
* 左移29位为:0110 0000 0000 0000 0000 0000 0000 0000
*/
private static final int TERMINATED = 3 << COUNT_BITS; private static int runStateOf(int c) {
return c & ~COUNT_MASK;
} private static int workerCountOf(int c) {
return c & COUNT_MASK;
} /**
* 根据runState和workerCount生成ctl,比如初始化线程池时,
* ctl = new AtomicInteger(ctlOf(RUNNING, 0)),代表线程池
* 的状态为RUNNING,存活线程数量为0。
* @param rs 线程池状态
* @param wc 存活线程数量
* @return
*/
private static int ctlOf(int rs, int wc) {
return rs | wc;
} /**
* 通过位运算在进行一些状态的判断时,我们不需要解析ctl的运行状态,
* 假设当前ctl:1110 0000 0000 0000 0000 0000 0000 0011,
* 我们要判断线程池状态是否小于STOP,由于ctl开头为1110,以补码的
* 方式来计算,ctl的值必然为负,STOP开头为0010,以补码方式计算为正数,
* 所以ctl必然小于STOP。
* ctl的布局还能保证workerCount永远不会为负数。
*/
private static boolean runStateLessThan(int c, int s) {
return c < s;
} /**
* 判断线程池至少处于某个状态,假设线程池现在队列为空且无任何存活线程,
* 所以能保证线程池处于TIDYING状态,如果s我们传入STOP,TIDYING的开头
* 为0100,STOP的开头为0010,TIDYING>STOP,所以我们能知道,线程池至少
* 处于STOP以上包含STOP的状态。
*/
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
} //线程池是否处于RUNNING状态
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
//CAS增加worker数量
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
//CAS减少worker数量
private boolean compareAndDecrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect - 1);
}
}
我们知道AbstractExecutorService.submit(...)方法最终会调用execute(Runnable command)方法,而AbstractExecutorService类中并没有实现execute(Runnable command)方法,它将execute(Runnable command)的实现交由子类。那么我们来看看ThreadPoolExecutor又是如何实现execute(Runnable command)方法呢?当一个任务提交到线程池,它的执行流程又是如何呢?来看下面的代码注释:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//如果工作线程的数量小于核心线程的数量,则尝试增加工作线程
if (workerCountOf(c) < corePoolSize) {//<1>
/*
* 如果成功增加工作线程,工作线程会执行我们提交的任务,我们就可以安心退出,
* 但线程池可以并发提交任务,可能存在在<1>处时工作线程小于核心线程数,
* 执行<2>处的addWorker(Runnable firstTask, boolean core)时,
* 其他线程先当前线程提交任务并增加工作线程,线程池内工作线程数超过核心线程数,
* 当前线程增加工作线程失败,不能直接退出。
* 注:addWorker(Runnable firstTask, boolean core),core为true
* 代表增加核心线程,会将任务作为firstTask传入;而false代表增加非核心线程,
* 如果传入firstTask为null,则代表让工作线程去队列中拉取任务。
*/
if (addWorker(command, true))//<2>
return;
//如果<2>处增加工作线程失败,则重新获取ctl的值。
c = ctl.get();
}
//判断线程池是否处于运行中,且任务可以入队成功,如果两者成立,则进入<3>分支
if (isRunning(c) && workQueue.offer(command)) {//<3>
int recheck = ctl.get();
/*
* 重新获取ctl,因为可能在进入<3>分支的时候,线程池被关闭,
* 所以要重新判断线程池状态,如果线程池不是处于运行状态,且
* 任务成功被移除,则进入<4>分支,拒绝任务。
*/
if (!isRunning(recheck) && remove(command))//<4>
reject(command);
/*
* 为什么这里要判断工作线程数量是否为0?因为如果设置allowCoreThreadTimeOut
* 为true的话,核心线程是可以为0的,可能代码执行到<3>处workQueue.offer(command)之前,
* 即任务还未入队,工作线程数量已经为0了,所以这里要重新根据ctl判断工作线程是否为0,
* 如果为0得再增加非核心线程去队列拉取并执行任务。
*/
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/*
* 如果没有进入<3>分支,而到达<5>分支,一般分两种情况:
* 1.线程池被关闭,<3>处isRunning(c)为false,此时调用<5>处的
* addWorker(...)必然返回false,然后执行拒绝策略。
* 2.线程池处于运行状态,<3>处isRunning(c)为true,但队列已满
* workQueue.offer(command)返回false,入队失败。
* 这时候应该尝试创建非核心工作线程执行任务,如果工作线程数量没到达最大线程数,
* 则创建线程并执行任务,如果工作线程到达最大线程数,则addWorker(...)返回
* false,执行拒绝策略。
*/
else if (!addWorker(command, false))//<5>
reject(command);
}
从ThreadPoolExecutor.execute(Runnable command)的实现,我们可以知道addWorker(Runnable firstTask, boolean core)方法是至关重要的,它决定了是否将任务添加进线程池执行。下面,我们再来看看addWorker(Runnable firstTask, boolean core)方法:
/**
* 此方法会根据线程池当前的运行状态、线程池所设定的边界(核心线程数和最大线程数)。
* 如果线程池允许创建线程执行任务,则创建线程执行firstTask并相应调整工作线程的数量。
* 如果线程池状态处于已停止(STOP)、关闭(SHUTDOWN)则会返回false。如果向线程工
* 厂请求创建线程失败,也会返回false。线程创建失败分两种情况,一种是线程工厂返回null,
* 或者执行Thread.start()时出现异常(通常为OOM异常)。
*
* @param firstTask:新线程首要执行任务,如果没有则传入null。当工作线程数少于核心 线程数,线程池总是创建一个新线程来执行firstTask。
* @param core:根据core为true或者false,决定是以核心线程数或者最大线程数作为界限, 判断当前线程池的工作线程池是否小于界限,如果小于则允许创建线程。
* @return 如果成功添加工作线程则返回true。
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (int c = ctl.get(); ; ) {//<1>
// Check if queue empty only if necessary.
/*
* 在这个地方addWorker(...)会返回false,即添加工作线程失败,
* 我们来看看是什么情况下会进入这个分支:
* runStateAtLeast(c, SHUTDOWN)代表线程池运行状态至少处于
* SHUTDOWN,如果线程池还处于RUNNING运行状态,此方法不会立即
* 返回失败。所以我们知道,要进入此分支,首要条件就是运行状态大于
* 等于SHUTDOWN。
* 之后如果runStateAtLeast(c, STOP)、firstTask != null、
* workQueue.isEmpty())这三个条件其一为true,则添加线程失败。
* 首先是runStateAtLeast(c, STOP),如果线程池当前处于STOP
* 状态,这时候既不接受新任务,也不处理队列里的任务,所以不管
* firstTask是否为null,都返回false。
* 如果runStateAtLeast(c, STOP)为false,那运行状态只能是
* SHUTDOWN,SHUTDOWN状态下会处理队列里的任务,但不再接受新
* 任务,所以firstTask不为null,也直接返回false。
* 如果运行状态既处于SHUTDOWN、firstTask也会空,且任务队列也
* 为空,则毫无必要增加工作线程,也直接返回false。
* 所以总结一下有两种情况不会进入此分支:
* 1.线程池处于RUNNING状态的时候。
* 2.线程池处于SHUTDOWN,但firstTask为空队列不为空时。
*/
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false; for (; ; ) {//<2>
//根据core判断工作线程的上限,如果大于上限则返回false。
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
/*
* 如果用CAS的方式成功增加工作线程的数量,则用break retry的方式
* 结束了retry对应的外层循环(即<1>处for循环),而不是break所在
* 的本层循环(即<2>处循环),代码会从<3>处开始执行。
*/
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
/*
* 如果上面用CAS的方式增加工作线程失败,则会重新判断线程池当前
* 状态是否至少处于SHUTDOWN,如果线程池已关闭,代码会跳到retry
* 处重新执行<1>处的for循环。如果线程池仍然处于RUNNING状态,则
* 重复执行<2>处的循环。
*/
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//<3>
boolean workerStarted = false;//如果工作线程启动成功,则赋值为true
boolean workerAdded = false;//如果工作线程添加成功则赋值为true
Worker w = null;
try {
//创建一个Worker对象,Worker对象会向线程工厂申请创建一个线程
w = new Worker(firstTask);
final Thread t = w.thread;
//如果线程工厂创建的Thread对象不为null,则进入此分支
if (t != null) {
/*
* 这里用可重入锁锁住try模块代码,因为要将之前创建好的
* w对象放进workers集合。
* 注:重入锁ReentrantLock的概念笔者会在以后的文章里
* 单独介绍,这里先简单理解,可重入锁就是禁止其他线程同时
* 访问mainLock.lock()到mainLock.unlock()之间的代码,
* 和synchronized有些类似。
*/
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
/*
* 重新获取ctl,判断线程池当前是否处于运行状态,或小于STOP状态,
* 即线程池处于RUNNING或SHUTDOWN,如果处于RUNNING则直接进入分支,
* 如果处于SHUTDOWN且首要执行任务为空,代表可能要启动一个工作线程
* 来执行队列中的任务。
*/
if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
/*
* 判断线程是否已经启动,如果使用的是Executors.DefaultThreadFactory
* 默认的线程工厂,正常来说创建出来的Thread对象都是线程未启动的,即:尚未
* 调用Thread.start()。但ThreadPoolExecutor允许我们传入定制化的线程
* 工厂,所以会存在线程工厂创建出Thread对象,但Thread对象已调用过start()
* 方法的可能。
*/
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//将创建好的worker添加进集合workers。
workers.add(w);
//更新历史上最大的工作线程数,即workers.size()。
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
//将worker添加进workers后,更新workerAdded的值为true。
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//如果worker成功加入集合,则启动线程,并更新workerStarted为true。
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
/*
* 如果worker没有启动,代表worker没有加入到workers集合,
* 可能线程池状态>=STOP,则需要执行添加工作线程失败操作。
*/
if (!workerStarted)
addWorkerFailed(w);
}
//返回工作线程是否启动成功
return workerStarted;
}
Java并发之ThreadPoolExecutor源码解析(二)的更多相关文章
- Java并发之ThreadPoolExecutor源码解析(三)
Worker 先前,笔者讲解到ThreadPoolExecutor.addWorker(Runnable firstTask, boolean core),在这个方法中工作线程可能创建成功,也可能创建 ...
- Java并发之ReentrantLock源码解析(二)
在了解如何加锁时候,我们再来了解如何解锁.可重入互斥锁ReentrantLock的解锁方法unlock()并不区分是公平锁还是非公平锁,Sync类并没有实现release(int arg)方法,这里会 ...
- Java并发之Semaphore源码解析(二)
在上一章,我们学习了信号量(Semaphore)是如何请求许可证的,下面我们来看看要如何归还许可证. 可以看到当我们要归还许可证时,不论是调用release()或是release(int permit ...
- Java并发之ReentrantReadWriteLock源码解析(二)
先前,笔者和大家一起了解了ReentrantReadWriteLock的写锁实现,其实写锁本身实现的逻辑很少,基本上还是复用AQS内部的等待队列思想.下面,我们来看看ReentrantReadWrit ...
- Java 1.7 ThreadPoolExecutor源码解析
Java中使用线程池技术一般都是使用Executors这个工厂类,它提供了非常简单方法来创建各种类型的线程池: public static ExecutorService newFixedThread ...
- Java并发之ReentrantLock源码解析(四)
Condition 在上一章中,我们大概了解了Condition的使用,下面我们来看看Condition再juc的实现.juc下Condition本质上是一个接口,它只定义了这个接口的使用方式,具体的 ...
- Java并发之Semaphore源码解析(一)
Semaphore 前情提要:在学习本章前,需要先了解笔者先前讲解过的ReentrantLock源码解析,ReentrantLock源码解析里介绍的方法有很多是本章的铺垫.下面,我们进入本章正题Sem ...
- Java并发之ReentrantReadWriteLock源码解析(一)
ReentrantReadWriteLock 前情提要:在学习本章前,需要先了解笔者先前讲解过的ReentrantLock源码解析和Semaphore源码解析,这两章介绍了很多方法都是本章的铺垫.下面 ...
- Java并发之ReentrantLock源码解析(三)
ReentrantLock和BlockingQueue 首先,看到这个标题,不要怀疑自己进错文章,也不要怀疑笔者写错,哈哈.本章笔者会从BlockingQueue(阻塞队列)的角度,看看juc包下的阻 ...
随机推荐
- Eclipse配置MySQL连接工具
1.项目名称右键新建文件夹lib 2.用鼠标将mysql-connector-java-5.1.15-bin.jar移动到lib文件夹中 3.选择Copy files点击OK 4.右键移动过来的mys ...
- 2015 Multi-University Training Contest 10(9/11)
2015 Multi-University Training Contest 10 5406 CRB and Apple 1.排序之后费用流 spfa用stack才能过 //#pragma GCC o ...
- Codeforces Round #579 (Div. 3) B Equal Rectangles、C. Common Divisors
B Equal Rectangles 题意: 给你4*n个数,让你判断能不能用这个4*n个数为边凑成n个矩形,使的每个矩形面积相等 题解: 原本是想着用二分来找出来那个最终的面积,但是仔细想一想,那个 ...
- hdu3635 Dragon Balls
Problem Description Five hundred years later, the number of dragon balls will increase unexpectedly, ...
- Codeforces Round #652 (Div. 2) B. AccurateLee(思维)
题意: 给你一个01字符串,现在你可以删除其中的一些子序列,要求如下:当遇到1 0的俩个连续子字符串后,可以删除其中的一个字符,现在要求把他删到尽量最短并且字典序最小,输出最后的字符串 题解: 刚开始 ...
- VS常用命令
1.查看Windows文件首部信息 dumpbin/headers 项目名称. 例如:dumpbin/headers test.exe 2.查看CLR首部信息 dumpbin/clrheader 项目 ...
- EDA : quartus2 17.1lite + modelsim +verilog 使用流程
首先 然后填充好自己写的代码 之后save as 存到自己的文件夹 会自动弹出 配置 Assignments settings 之后第一次编译 成功后processing star ...
- 解决debian (Friendly ARM 嵌入式板)的sudo等一部分命令无法TAB补全
TAB对于比较长的命令在使用时是十分方便的,最近就遇到TAB 键无法补全sudo后跟的命令的情况因此去网上取经.在一篇博客中找到解决问题的方法,觉得大牛们写的太精炼然后自己做如下总结方便自已以后解决类 ...
- 智能广告投放平台 All in One
智能广告投放平台 All in One app demos 知之数据 一站式广告营销平台 https://hio.cn/ refs https://www.jonmellman.com/posts/p ...
- DLL & Dynamic-link library
DLL & Dynamic-link library 动态链接库 .dll 动态链接库(英语:Dynamic-link library,缩写为 DLL)是微软公司在微软视窗操作系统中实现共享函 ...