一:程池的作用
线程池可以使线程复用,避免了每次线程都new一个新的线程,另外我们可以给线程池一个固定大小,从而避免了大量线程对CPU的占用。
二:线程池的创建
在Java用有一个Executors工具类,可以为我们创建一个线程池,其本质就是new了一个ThreadPoolExecutor对象,
ThreadPoolExecutor参数最全的构造方法:
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:线程池的核心线程数,当线程池中的工作线程数小于核心线程数的时候,只要向线程池指派任务,线程池就会创建工作线程。
maximumPoolSize:线程池最大工作线程数,当线程池中的工作线程达到最大数的时候,即使再向线程池指派任务,线程池不会创建工作线程。
keepAliveTime:当线程池的工作线程数大于核心线程数的时候,多余核心线程数的部分线程可以保持keepAliveTime的空闲时间,当keepAliveTime时间内还没有获取到任务,这些线程后就会被回收。
unit:保持空闲时间的时间单位。
workQueue:任务队列,当线程池里面核心线程都在工作的时候,再向线程池指派任务,线程池会将任务放入任务队列里,工作线程在执行完任务后会再向任务队列里取出任务来执行。
threadFactory:创建执行任务的工作线程的线程工厂。
handler:拒绝任务加入线程池的策略,当线程池里的线程已经达到最大数后,再向线程池里加派任务时,线程池会拒绝执行这些任务,handler就是具体执行拒绝的对象。
线程池的大体工作思路:
(1).当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
(2).当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
(3).当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
(4).当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
(5).当线程池中超过corePoolSize数的线程,空闲时间达到keepAliveTime时,关闭空闲线程
(6).当设置allowCoreThreadTimeOut(true)时,线程池中核心线程空闲时间达到keepAliveTime也将关闭
线程池的执行流程:
任务被提交到线程池,会先判断当前线程数量是否小于corePoolSize,如果小于则创建线程来执行提交的任务,否则将任务放入workQueue队列,如果workQueue满了,则判断当前线程数量是否小于maximumPoolSize,如果小于则创建线程执行任务,否则就会调用handler,以表示线程池拒绝接收任务。
线程池池五中创建方式:
(1)1SingleThreadExecutor : 只有一个线程的线程池,因此所有提交的任务是顺序执行,
代码: Executors.newSingleThreadExecutor()
(2) Cached Thread Pool : 线程池里有很多线程需要同时执行,老的可用线程将被新的任务触发重新执行,如果线程超过60秒内没执行,那么将被终止并从池中删除,
代码:Executors.newCachedThreadPool()
(3)FixedThread Pool: 拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待,
代码: Executors.newFixedThreadPool(4)
在构造函数中的参数4是线程池的大小,你可以随意设置,也可以和cpu的数量保持一致,获取cpu的数量int cpuNums =Runtime.getRuntime().availableProcessors();
(4)ScheduledThreadPool : 用来调度即将执行的任务的线程池,
代码:Executors.newScheduledThreadPool()
(5)SingleThread Scheduled Pool : 只有一个线程,用来调度执行将来的任务,
代码:Executors.newSingleThreadScheduledExecutor()
三. 线程池方法源码解读
(1)参数解读
/*
*可以将这个参数看成是一个三十二位的二进制数,
*其中前三位表示线程池的状态,
*后二十九位表示线程池中工作线程的数量
*/
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
//CAPACITY值为:00011111111111111111111111111111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//RUNNING状态表示线程池可以接受任务正常工作
private static final int RUNNING = -1 << COUNT_BITS;
//SHUTDOWN状态表示线程池不接受任务,但如果阻塞队列中还有任务,会将阻塞队列中的任务执行完
private static final int SHUTDOWN = 0 << COUNT_BITS;
//STOP状态表示线程池不接受任务,也不会执行阻塞队列中的任务,即使阻塞队列中还存在任务
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
//获取线程池的状态,一般会用形参rs表示,在后面rs参数一般表示线程池状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取线程池工作线程的数量,一般用形参wc表示
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
(2)execute方法
execute方法是线程池执行任务的入口,execute方法的入参是必须要实现了Runnable接口的实现类
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
/*
*获取线程池中工作线程的数量和核心线程数做比较
*如果小于就调用addWorker方法,
*
*如果失败了会重新获取工作线程数量和线程池状态,成功就直接返回
**/
if (workerCountOf(c) < corePoolSize) {
/*
* 此时调用addWorker方法就是创建一个新的工作线程来执行这个任务
* 向addWorker方法传入参数true,会在addWorker方法里面继续将工作线程数量和核心线程数做比较。
*/
if (addWorker(command, true))
return;
c = ctl.get();
}
/*
* 程序运行到这里的前提条件有两个
* 1.工作线程数量大于核心线程数量
* 2.调用addWorker方法失败
* 如果线程池是running状态,而且任务加入阻塞队列成功,执行if了里面的代码
* if里面的代码其实是对加入到阻塞队列的任务的条件的一个后续判断,这句话比较绕
* 意思是将任务放到阻塞队列了后,我还要判断是否应该将这个队列放到阻塞队列中
* 这里为什么后续还要加判断我也不是很懂。干嘛不加在前面呢?
*/
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
/*
* 重新判断线程池的工作状态,如果不是running状态,就将任务从队列中移除
* 移除后调用线程池的拒绝策越,默认的是抛出异常
*/
if (! isRunning(recheck) && remove(command))
reject(command);
/*
* 执行到这的前提条件:线程池的状态是running
* 如果将任务加入到了队列中,却发现线程池中已经没有工作线程了怎么办?
* 我刚开始对线程池中没有工作线程还存在疑问,当allowCoreThreadTimeout设置成true的时候
* 核心线程有可能在空闲时间超时后被回收,所以线程池存在工作线程数为0的情况
* ,或者核心线程数设置为0也会出现这个情况
*/
else if (workerCountOf(recheck) == 0)
/*
* 传false将会在addWorker方法中判断线程池的工作线程数量和最大线程数量做比较
* 传一个空的任务,开启一个工作线程,但这个工作线程会发现当前的任务是空,然后会去队列中取任务
* 这样就避免了线程池的状态是running,而且队列中还有任务,但线程池却不执行队列中的任务
*/
addWorker(null, false);
}
/*
* 程序执行到这的前提条件有两个:
* 1.线程池的工作状态不是running
* 2.任务加入到队列失败了
* 如果是第一种情况线程池的工作状态不是running了,那调用addWorker方法也会返回false,
0 * 就会调用拒绝执行任务的策越
* 如果是第二种情况,加入队列失败,这说明队列已经满了,那调用addWorker方法,参数传fasle
* 表示会将线程池的工作线程数量和最大线程数量比较,如果小于就会创建新的工作线程来执行这个任务
*/
else if (!addWorker(command, false))
reject(command);
}
execute方法比较绕,不是很直观,因为它的判断条件有点多,我感觉写法比较怪异。在execute方法中没有看到要执行任务的地方,在这个方法里只是做了条件判断,如果满足条件的话,就交给addWorker处理,并且根据判断条件的不同,传给addWorker方法的参数也不同。总结一下execute方法的执行逻辑
1)当工作线程数量小于核心线程数量的时候,会将任务交给addWorker方法,addWorker方法会创建新的线程来处理这个任务
2)当工作线程数量大于和核心线程数量并且线程池的工作状态是running的时候,会将任务放入到阻塞队列中
3)当阻塞队列已经满了,会将任务交给addWorker方法处理
4)当交给addWorker方法处理失败或是线程池的状态不是running的时候,会调用线程池的拒绝策越。
execute方法其实是线程池工作的一个大体思路,具体细节我们可以看看addWorker方法。
(3)addWorker方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
/*
* 可以将if条件里面的判断条件这样看:
* rs >= SHUTDOWN &&(rs != SHUTDOWN ||firstTask != null || workQueue.isEmpty())
* 所以在这里能进if,让addWorker返回false的情况有这样几种
* 1.当线程池的状态是stop
* 2.当线程池的状态是shutdown的话,firstTask不为空
* 3.当线程池的状态是shutdown的话,队列是空的
* 以上三种情况返回false
*/
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
/*
* 当传回core传true的时候,比较当前线程池工作线程数和核心线程数做比较
* 当传回core传false的时候,比较当前线程池工作线程数和最大线程数做比较
* 如果当前线程数都是大于等于他们的,直接返回false
*/
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//利用cas函数增加线程池工作线程数,如果成功就直接跳出这两层循环
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 w = null;
try {
final ReentrantLock mainLock = this.mainLock;
//Worker是线程池的一个内部类,其实完成任务和从队列中取任务都是在Worker中完成的
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
mainLock.lock();
try {
int c = ctl.get();
int rs = runStateOf(c);
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//当将任务放到任务队列(不同于阻塞队列)成功后,启动工作线程,执行firstTask任务
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
可以看到任务的启动是在addWorker方法是生成一个新的工作线程来开启任务。Worker就将工作线程和任务封装到了自己内部,我们可以将Worker看成就是一个工作线程,至于Worker是如何执行任务和从阻塞队列中取任务,那就是Worker的事了
(4)内部类Worker代码
/**
* Class Worker mainly maintains interrupt control state for
* threads running tasks, along with other minor bookkeeping.
* This class opportunistically extends AbstractQueuedSynchronizer
* to simplify acquiring and releasing a lock surrounding each
* task execution. This protects against interrupts that are
* intended to wake up a worker thread waiting for a task from
* instead interrupting a task being run. We implement a simple
* non-reentrant mutual exclusion lock rather than use
* ReentrantLock because we do not want worker tasks to be able to
* reacquire the lock when they invoke pool control methods like
* setCorePoolSize. Additionally, to suppress interrupts until
* the thread actually starts running tasks, we initialize lock
* state to a negative value, and clear it upon start (in
* runWorker).
*/
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
// ...
/** 工作线程 */
final Thread thread;
/**任务 */
Runnable firstTask;
/**
* 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);
}
// ...
}
这里我只贴出了 Worker类中和本次分析有关的代码, 其他代码就省略掉了. Worker类内部还包含一个线程对象 thread 和一个 Runnable对象 firstTask. 而这个线程对象的创建过程见35行, 将Worker对象自身作为这个线程对象 thread的 Runnable参数传递给 thread的构造方法. 那么我们就可以知道, 如果要运行该线程, 也就是执行 thread.start(), 那么实际上就是要执行 thread所在的 Worker类中的 run()方法, 而从第40行又可以知道, Worker类中的 run()方法又是调用 runWorker(this); 这个方法的, 并将 thread所在的 Worker对象作为这个 runWorker()方法的参数. 简单来说就是:
启动一个 Worker对象中包含的线程 thread, 就相当于要执行 runWorker()方法, 并将该 Worker对象作为该方法的参数.
(5)runWorker方法
/*
* 这个方法是执行任务,当前任务执行完后会向队列里面取任务,
* 如果队列里也没任务了,就会将这个工作线程从工作线程队列中移除
* 所以线程池几个线程执行多个任务就在这里体现了,而不是
* 一个线程执行一个任务
*/
final void runWorker(Worker w) {
//工作线程
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//任务执行完后,getTask方法接着从队列中取任务
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 {
//如果没有继承ThreadPoolExecutor实现这个方法,这个方法是没有执行动作的
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 {
//如果没有继承ThreadPoolExecutor实现这个方法,这个方法是没有执行动作的
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//走到这里说明队列里的任务都已经被执行完,移除工作线程
processWorkerExit(w, completedAbruptly);
}
}
(6)getTask方法
import java.util.concurrent.TimeUnit;
/*
* 从阻塞队列中取任务,取任务有三种情况发生
* 1.渠道任务并返回任务
* 2.没有取到任务,返回null,这个工作线程被回收
* 3.没有取到任务,阻塞在向阻塞队列取任务这里
* 第三点就是线程池中的空闲任务是如何存在的
*/
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//当线程池的工作状态是stop,就减少工作线程数,返回null
//当线程池的工作状态是SHUTDOWN并且队列是空的时候,就减少工作线程数,返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
boolean timed; // Are workers subject to culling?
for (;;) {
int wc = workerCountOf(c);
/*
* 当允许核心线程超时后被收回或者是工作线程数大于核心线程数
* 这两种情况下都是一定要回收工作线程的
*/
timed = allowCoreThreadTimeOut || wc > corePoolSize;
/*
* 第一次执行这个语句的时候,是一定会进if里面,跳出里面这层循环的,因为初始化的timedOut=false
* 当不进入这个循环,说明工作线程超时了,工作线程超时一般会返回null;
*/
if (wc <= maximumPoolSize && ! (timedOut && timed))
break;
/*
* 说明工作线程超时了,工作线程超时一般会返回null;
* 减少工作线程数量
*/
if (compareAndDecrementWorkerCount(c))
return null;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
try {
/*
* timed为true的时候,当工作线程取任务超时就会返回,返回后会被回收
* 当timed为false的时候,说明当前工作线程不需要被回收,所以就可以在向阻塞队列取任务的时候被阻塞
* 这里就提现了线程池中空闲的线程是如何存在的
*/
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
/*
* 走到这里,r一定为空,最后会进入到第40行的if里面,还是返回null
* 当返回null,这个工作线程就会被回收
*
*/
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
(7)processWorkerExit方法
/**
* 减少工作线程数,将工作线程从工作线程队列中移除
*/
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
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; // replacement not needed
}
addWorker(null, false);
}
}
通过源码,不仅知道了线程池的工作原理,而且知道了工作线程是如何执行任务的,当工作线程空闲的时候,线程池是如何处理的。