一周连肝三篇,也算是对之前Java并发知识的一个集中梳理,前两篇见:Java线程安全,Java并发容器
这篇来看看线程池的实现原理,话不多说,先上个核心流程图:
其实ThreadPoolExecutor的设计和AQS理念有点类似,都是一个模板框架,把核心的功能都已经实现组装好了,使用者只需要动态配置一些参数或者子类化实现一些方法就能实现差异化的功能,对扩展开放的良好表现。
下面就结合源码看看ThreadPoolExecutor核心的几块内容吧,如图中所示,ThreadPoolExecutor主要包含核心线程策略、阻塞队列、最大线程数策略、RejectHandler这四块内容,也是个性化扩展涉及到的主要方向。
Worker
在此之前先要注意以下线程池状态和Worker数量维护,都是通过ctl这个成员属性来维护的,采用了高三位保存线程池状态,其他位保存Worker数量的技巧,由于是AtomicInteger,所以在操作值的时候可以很方便使用CAS操作保证线程安全,后续Worker数量的增减都是通过该变量与设置的核心线程数、最大线数比较来判断的,同时在对于Worker Set的维护以及线程池中断的一些操作采用了ReentrantLock的方式来保证线程安全。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
private final ReentrantLock mainLock = new ReentrantLock();
/**
* Set containing all worker threads in pool. Accessed only when
* holding mainLock.
*/
private final HashSet<Worker> workers = new HashSet<Worker>();
/**
* Wait condition to support awaitTermination
*/
private final Condition termination = mainLock.newCondition();
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
...//Worker Thread启动过程略
return workerStarted;
}
然后Worker主要作用就是不断从阻塞队列中获取Task执行,Worker Thread核心代码如下:
//ThreadPoolExecutor内部类,继承了AQS,实现了Runnable方法
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
// Lock methods
...
}
//运行Runnable
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
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 {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
从源码可以看出:
1、Worker持有的thread对象是通过ThreadFactory创建的,所以Thread的创建机制也会影响Worker Thread
2、Worker的核心逻辑是通过while循环+BlockingQueue的阻塞机制来实现对任务的执行,同时通过自身继承的AQS机制保证任务执行的线程安全,这边有个细节点可以思考下,为什么要使用Worker.lock?使用ThreadPoolExecutor的mainLock不行么?其实本来ctl就是线程安全的,这块代码不用锁也行,但是由于shutdown方法强制退出时会遍历Worker尝试中断,这边的锁是为了runWork和interrupt操作之间的线程安全,所以用了Worker.lock
WorkerQueue&RejectHandler
WorkerQueue主要是维护任务队列,BlockingQueue的原理就不多废话了,这边有个细节就是WorkerQueue为空时有几种情况:
1、如果当前线程小于核心线程数并且不允许核心线程超时(countOfWorker<corePoolSize&&allowCoreThreadTimeOut=false),getTask就会采用阻塞方法将Worker线程挂起(workQueue.take())
2、否则getTask都会采用超时阻塞的方式获取任务(workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)),如果超时时间内有新的任务获取则执行,否则Worker线程将结束
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
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 {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
RejectHandler主要就是要理解线程池已满的条件:WorkerQueue已满 && Worker数量大于等于最大数量,所以说如果WorkerQueue是无限队列或者maximumPoolSize无限都将无法触发reject逻辑,这边也再提一句如果WorkerQueue是无限队列,maximumPoolSize也将不起作用,因为超出核心线程数量的任务都将加入WorkerQueue。RejectHandler主要有以下几种:
AbortPolicy:添加任务时直接抛出RejectedExecutionException异常,默认就是这种策略
CallerRunsPolicy:直接在调用线程执行
DiscardPolicy:丢弃添加的任务
DiscardPolicy:丢弃WorkerQueue中最早添加的任务
一般来说,DiscardPolicy策略可能更符合要求,需要具体业务决定。
常用线程池
Java提供了Executors创建常用的几种线程池:
newFixedThreadPool:固定核心线程数+无界阻塞队列,所以满负载的情况下只会有固定核心线程数的Worker,并且不会触发Reject策略,适用于比较稳定的异步任务处理
newSingleThreadExecutor:固定核心线程数位1+无界阻塞队列
newCachedThreadPool:核心线程数为0+无限最大线程数+SynchronousQueue无容量队列+60s keepAliveTime,所以每次提交任务都会创建一个线程,适用于那些不固定的任务流,但是注意过载会不断创建线程直到资源耗尽
FutureTask:可以获取返回值&取消的Runnable
FutureTask的作用主要时采用装饰者模式包装了原始的Runnable or Callable,使其获得了返回执行结果+取消执行的功能。
FutureTask
源码如下:包括一些小的细节,如执行run方法的时候设置runner对象,WaitNode的维护等等
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
总结
线程池的核心知识点主要就那几大块,其实从这些源码来看,有很多细节值得我们注意,比如随处可见的CAS操作、锁的粒度问题、通过无限循环来保证逻辑的顺利执行等等。通过这三篇文章的回顾,对Java多线程的知识也差不多有个完整的认识了,大概先到这吧~