Java线程池适用于高并发场景下需要多线程处理业务的时候适用,线程池并不能直接提高线程执行任务的速度。但可以通过减少线程频繁地创建与销毁来提高间接整个系统的性能,并方便管理。
《阿里巴巴Java开发手册》规定:线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:
使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
首先我们看一下类的关系:
简单分析一下类结构,顶层接口是Executor方法,有一个execute(Runnable command)方法,ExecutorService是它的子接口,增加了shutdown(), submit方法等等,AbstractExecutorService实现了ExecutorService接口,是一个抽象类,里面具体实现了submit等方法。ThreadPoolExecutor类实现了顶层接口的execute方法,实现了ExecutorService的shutdown等方法(submit没有覆盖,使用的话会直接调用其父类的,submit方法里调用了execute方法,不同的是可以获得返回值)。
我们所要分析的核心类是ThreadPoolExecutor,首先看其构造方法里的七个参数:
1.corePoolSize(int),核心线程数量
2.maximumPoolSize(int),最大线程池数量
3.keepAliveTime(long),线程最大空闲时间
4.unit(TimeUnit),时间单位
5.workQueue(BlockingQueue<Runnable>),线程等待队列
6.threadFactory(ThreadFactory),线程创建工厂,ThreadFactory是一个接口,有一个newThread方法,线程池里默认用Executors工厂里的DefaultThreadFactory创建这个工厂。其产生线程仍然是用的是new Thread对象的方法。
7.hanlder(RejectedExecutionHandler),拒绝策略
四种拒绝策略均为内部类:AbortPolicy,默认策略,抛出RejectedExecutionException异常。
CallerRunsPolicy,由调用线程(提交任务)来处理。
DiscardPolicy,直接丢弃新任务。
DiscardOldestPolicy,丢弃队列最前面的任务,之后重新提交新任务。
线程池状态:
RUNNING:运行态, 可处理新任务并执行队列里的任务
SHUTDOWN:关闭态,不接受新任务,但可处理队列里的任务。
STOP:停止态,不接受新任务,不处理队列中的任务,且打断运行中的任务。
TIDYING:整理态,所以任务已结束,workCount = 0,将执行terminated()方法
TERMINATED:结束态,terminated()方法已完成。
关于对这些状态的解读,请看下面的代码:
//ctl : 1110 0000 0000 0000 0000 0000 0000 0000
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//COUNT_BITS = Integer.SIZE - 3 = 29
private static final int COUNT_BITS = Integer.SIZE - 3;
/*CAPACITY = (1 << COUNT_BITS) - 1
* 0000 0000 0000 0000 0000 0000 0000 0001 << 29
* 0010 0000 0000 0000 0000 0000 0000 0000, 再减1
* 0001 1111 1111 1111 1111 1111 1111 1111 值为536,870,911,2^29 -1 ,即最大容量
*/
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
/*运行态,可处理新任务并执行队列中的任务
* 1111 1111 1111 1111 1111 1111 1111 1111 << 29
* 1110 0000 0000 0000 0000 0000 0000 0000 值为-536810912
*/
private static final int RUNNING = -1 << COUNT_BITS;
/*关闭态,不接受新任务,但处理队列中的任务
* 0000 0000 0000 0000 0000 0000 0000 0000 << 29
* 0000 0000 0000 0000 0000 0000 0000 0000
*/
private static final int SHUTDOWN = 0 << COUNT_BITS;
/*停止态,不接受新任务,不处理队列中任务,且打断运行中任务
* 0000 0000 0000 0000 0000 0000 0000 0001 << 29
* 0010 0000 0000 0000 0000 0000 0000 0000,值为536,870,912
*/
private static final int STOP = 1 << COUNT_BITS;
/*整理态,所有任务已经结束,workerCount=0 , 将执行terminated()方法
* 0000 0000 0000 0000 0000 0000 0000 0010 << 29
* 0100 0000 0000 0000 0000 0000 0000 0000,值为1,073,741,824
*/
private static final int TIDYING = 2 << COUNT_BITS;
/*结束态, terminated()方法已完成
* 0000 0000 0000 0000 0000 0000 0000 0011 << 29
* 0110 0000 0000 0000 0000 0000 0000 0000, 值为1,610,612,736
*/
private static final int TERMINATED = 3 << COUNT_BITS;
//取前三位
private static int runStateOf(int c) { return c & ~CAPACITY; }
//取后29位
private static int workerCountOf(int c) { return c & CAPACITY; }
/*RUNNING : 1110 0000 0000 0000 0000 0000 0000 0000
* wc : 0000 0000 0000 0000 0000 0000 0000 0000
* ctl : 1110 0000 0000 0000 0000 0000 0000 0000
*/
private static int ctlOf(int rs, int wc) { return rs | wc; }
由上面的代码可以看得出来,ctl是一个原子整数类,用作标识,ctl会随着线程池地运行进行CAS转换,其初值和RUNNING相同,也就是说线程池生成后初始状态就是RUNNING。几个整型量与COUNT_BITS作位运算可以得到这五种状态以及一个最大容量CAPACITY。ctl的后29位代表线程数量,前3位代表线程池状态(五种状态需要三个位来表示)。通过runStateOf方法可以获得其前三位,通过workerCountOf方法可以获得其后29位。ctl的初值为ctlOf(RUNNING, 0),其值等于RUNNING,也代表线程池生成时的状态。
我们直接看其七参的构造方法 (其他构造均由这个方法生成,无ThreadFactory时用Executors.defaultThreadFactory,无handler时用AbortPolicy):
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
下面看一下execute方法:
public void execute(Runnable command) {
//判断任务是否存在
if (command == null)
throw new NullPointerException();
//获取ctl,以得到线程池的工作线程数量以及线程池状态
int c = ctl.get();
//若工作线程数量小于核心线程数量,则通过addWorker方法增加线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
//上述操作后ctl可能已经更改,重新获得ctl
c = ctl.get();
}
//检测线程池的状态是否是RUNNING态,若是则向阻塞队列中添加任务
if (isRunning(c) && workQueue.offer(command)) {
//再次检测,有可能此时线程已经不是RUNNING状态,不是则需要丢弃新任务
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
//执行拒绝策略
reject(command);
//没有可用工作线程,则增加新的工作线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//添加非核心线程执行任务,若失败说明线程池已经饱和了,或者shutdown了,执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
当线程数量没有达到核心线程数量的时候(即第一个if成立),会创建新线程来执行当前任务(即addWorker),并返回。
如果线程数量达到或超过了核心线程数量,会进入第二个if里,此时需要检测线程池是否是RUNNING状态,如果是的话向阻塞队列里面添加这个任务(if (isRunning(c) && workQueue.offer(command))成立)。此时要再进行检测,有可能此时线程池已经不是RUNNING状态(比如执行了shutdownNow让线程池变成了STOP状态),不是则要移除这个任务,并且执行拒绝策略。如果此时没有工作线程了,则创造一个工作线程。
若不在RUNNABLE状态或阻塞队列里添加任务失败,添加非核心线程执行任务,若失败(则说明线程池饱和或线程池关闭,执行拒绝策略。
下面来看addWorker类
private boolean addWorker(Runnable firstTask, boolean core) {
//retry标记与goto类似,下面有两处retry标记
retry:
for (;;) {
//获取ctl,并由ctl获取线程池运行状态
int c = ctl.get();
int rs = runStateOf(c);
//大于SHUTDOWN说明是处于STOP或TIDYING或TERMINATED状态,拒绝增加线程,直接返回
//进一步判断若处在SHUTDOWN状态但是任务为空或队列为空仍不用增加线程
//SHUTDOWM状态若还有任务需继续处理
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
//由ctl获得工作线程数量
int wc = workerCountOf(c);
/*大于最大容量,或根据core比较核心线程数/最大线程数是否达到
*即调用addWorker方法的时候根据core判断若添加的是核心线程,那么如果总线程量超过核心线程最大
*数量,则不创建线程,直接返回。若判断添加的非核心线程,如果总线程量超过最大线程量,不创建,直接返回。
*/
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//CAS操作增加线程数量
if (compareAndIncrementWorkerCount(c))
//此时外层循环会结束
break retry;
//CAS操作不成功,则检测当前线程池状态与开始是否一致,不一致则重新执行最外层for循环
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
//结束当前循环,跳入外层循环重新执行
continue retry;
}
}
//准备一些数据
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//new一个Worker并将任务添加进去
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
//加锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//加锁状态下检测
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//检测线程是否可用
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//线程可用则存入HashSet中(全局变量)
workers.add(w);
//给全局变量largestPoolSize赋值以记录最大线程工作数量
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
//解锁
mainLock.unlock();
}
//前面代码没出现什么问题,worker已经创建则启动,并标记worker已启动
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//若woker未启动,执行添加woker失败方法
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
线程启动失败,会移除由表中Woker,之后用CAS操作更新ctl的值,调用tryTerminate中止线程池。
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w);
decrementWorkerCount();
tryTerminate();
} finally {
mainLock.unlock();
}
}
再向下探究Woker类(这个类是ThreadPoolExecutor的内部类):
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
这个类实现了RUNNABLE接口,继承了AQS同步器(这里不展开说)。
构造方法与run方法:
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
可以看到其创建线程的方式是通过ThreadPoolExecutor的线程工程的newThread方法实现的,本质还是new 一个Thread对象。
runWorker方法:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
//允许中断
boolean completedAbruptly = true;
try {
//task开始由Woker获得,是firstTask,后面由阻塞队列里取
while (task != null || (task = getTask()) != null) {
w.lock();
//检测线程池状态
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
//开始任务,执行RUNNABLE的run方法
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);
}
}
beforeExecute与afterExecute方法在ThreadPoolExecutor类里为空方法,在线程运行的前后执行,可以在子类里去覆盖,提供了扩展的可能性。
让我们看一下getTask方法与processWorkerExit方法:
private Runnable getTask() {
//用作检测超时
boolean timedOut = false;
for (;;) {
//取得ctl,并由它取得线程池状态
int c = ctl.get();
int rs = runStateOf(c);
//检查线程池与阻塞队列状态,若线程池状态为SHUTDOWN,同时工作队列为空,
//或是另外三种状态(非RUNNING),返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
//由ctl获取工作线程数量
int wc = workerCountOf(c);
//如果允许核心线程退出,或工作线程数量大于核心线程数量,即存在非核心线程,为true
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//超时或线程数量超标,返回null
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//由上面的timed判断提取队列任务是否有等待时间(无等待时间会一直阻塞)
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//运行此方法证明线程中断,减少线程数量
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//记录完成任务数量,并从列表中移除任务
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
//尝试中止线程池
tryTerminate();
//检测线程池状态
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;
}
//由于线程中断,需要创建线程去执行任务
addWorker(null, false);
}
}
在取任务的时候,如果核心线程为未满,并且我们不给全局变量allowCoreThreadTimeOut 赋值为true(默认赋值为false),那么就会采用take()方法从阻塞队列中取任务,哪怕当前已经没有任务可做,且有空余线程未执行任务,take()方法也能保证在调用getTask方法的线程一直阻塞,从而保证了核心线程不会被释放掉。我们阅读源代码可以发现,线程池本身不会对它生产出的线程标记为核心或非核心。而是通过这种方式动态保证核心线程池的数量。
总结:
线程池执行的时候,是通过new一个个Woker并传入Runnable对象来执行我们的任务的,线程池会优先让核心线程直接执行任务,任务数超过核心线程会被放入阻塞队列中,完成任务的线程继续从队列里取任务来要执行。若队列满,线程池会增加更多的Worker去直接执行后来的任务(也就是说进队列的任务有可能会比新任务执行得晚)。如果最大线程数量已满,则执行拒绝策略。当线程无任务可做并且闲置时间超过我们设置的时间的时候,会被线程池销毁(线程总数小于核心线程数除外)。并且线程池通过由队列取得任务时的阻塞来保证核心线程的数量。在整个过程中线程池会经常检测自身状态以执行不同的方案。
看了源代码,不由得感叹创造java源代码的大神的鬼斧神工。其中许许多多的地方值得我去学习,如位运算的应用以及高并发场景下线程安全的处理。