有图可知,线程池的核心组成部分分别为:线程列表和阻塞队列。
1、线程列表:这个很容易理解,就是一堆线程,用一个列表存储起来。每次都复用列表中的线程来执行任务,而不需要重新创建新的线程。
2、阻塞队列:为什么需要一个阻塞队列呢,普通的队列行不行呢? 之所以阻塞队列,主要的原因是平衡【放任务】和【拿任务】之间的速率。
如果【放任务】的速度快,但是【拿任务】的速度慢,那么就需要一个队列来存储多余的任务。
如果【拿任务】的速度快,但是【放任务】的速度慢,那么就需要线程等待新的任务,因此就需要一个阻塞的地方,让线程等待。
因此通过对上述两点的分析,需要的是一个有阻塞功能的队列,BlockingQueue。
下面对上述的模型进行分析:
1、阻塞队列:在线程池中,阻塞队列都需要什么属性,需要什么功能呢?
首先,需要一个list来存储相应的任务对象。
第二,需要存储一把锁,因为【放任务】和【拿任务】可能有多个线程在同时执行
第三,需要等待条件,【放任务】的线程等待【队列非满】,而【拿任务】的线程等待【队列非空】
第四,需要一个get任务的方法,和一个put任务的方法。这个两个方法应该是互斥加锁的,并且在不同的条件下等待。详细可参考:五、详解ReentrantLock-CSDN博客
2、线程列表:线程列表就是一个list类型,每个元素就是一个线程。当然为了做一下特殊逻辑,可以用Thread的父类来进行封装。二、线程创建与运行-CSDN博客
上述的模型为线程池的核心模型,在真正生产环境中,需要根据不同的需求在核心模型的基础上增加其他的功能,例如核心线程数,最大线程数,最大队列长度,等待时间,拒绝策略,线程工厂等等。
那么下面我们开始介绍在生产环境中,真正使用的几种线程池。
在具体讲解源码之前,先介绍一下ThreadPoolExecutor中的ctl变量。ctl是一个原子整数(AtomicInteger),它同时包含了线程池的运行状态和线程池中工作线程的数量。
其高三位为当前线程池的状态,低29位为当前线程池中的线程数。在TheadPoolExecutor中调用ctlOf方法将 状态和个数拼接起来。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
那么为什么ThreadPoolExecutor要用一个int变量来表示两个概念,而不是用两个int变量来分别表示线程池状态和线程个数呢?
其主要原因为了并发考虑,因为线程池状态和线程数量在一个int中,只需要一次cas就能将两个含义同时原子的更新。如果用两个变量,需要保证两个变量的一致性。
running : 当线程池处于running状态的时候,用户可以向线程池中添加新任务,并且线程正常处理阻塞队列中的任务。
shutdown:当线程处于shutdown状态的时候,用户不能像线程池中添加新的任务了,但是线程依然能够将队列中的任务执行完毕
stop:当线程处于stop状态的时候,用户无法向线程池提交新的任务,并且线程也不会继续执行队列中的任务。
先从创建一个ThreadPoolExecutor来观察,线程池都需要什么样的参数。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue 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;
}
1、corePoolSize:核心线程数,意味着当正常情况下,线程池只有corePoolSize个线程在运行
2、maximumPoolSize: 最大线程数,可以理解为救急线程,当线程池中的线程数已经等于corePoolSize,并且阻塞队列中已经满足,那么就继续扩容线程,直到线程数为maximumPoolSize
3、keepAliveTime: 表示那些创建的救急线程需要空转多长时间,如果超过这个时间,救急线程将被回收
4、TImeUnit:时间单位
5、workQueue: 阻塞队列。请看核心模型
5、threadFactory:创建线程的工厂,如果不设置,将会分配一个默认的工DefaultThreadFactory
6、handler:拒绝策略。当核心线程数满了,阻塞队列满足,救急线程也满了,那么当前put任务的线程要执行这个handler,来确认如何处理。
常见的拒绝策略有:AbortPolicy,CallerRunsPolicy,DiscardOldestPolicy, DiscardPolicy。后面详细分析一下上述四个拒绝策略的代码。
该方法用于提交一个可执行的任务,这个是我们在生产环境最常用的一个方法之一。接下来具体分析线程池是如何执行的。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
1、首先从ctl原子变量中获取值,然后通过workerCountof函数从ctl中获取低29位,计算出线程数。我们之前讲过ctl高三位记录线程池状态,低29位记录线程个数。
private static int workerCountOf(int c) {
return c & COUNT_MASK;
}
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
private static final int COUNT_BITS = Integer.SIZE - 3;
上述代码COUNT_BITS = Integer.SIZE-3。 Integer.SIZE表示Integer类型的大小,即占用几个bit位,所以COUNT_BITS=32-3 = 29。
COUNT_MASK = (1<<29)-1, 即将数字1左移29位,即10000000000(后面29个0),然后再-1,那就是29个1。
因此workerCountOf(c)方法的逻辑就是将c与29个1向&操作,结果就是后29bit的值。
2、之后判断当前线程数是否小于corePoolSize。如果小于,那么执行addWork方法。如果addWorker添加成功,则直接返回,否则,执行下一个if
我们重点分析addWork方法,这个方法的意思是,添加一个worker,其实本质上就是添加一个线程。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (int c = ctl.get();;) {
// Check if queue empty only if necessary.
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;
for (;;) {
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
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();
if (isRunning(c) ||
(runStateLessThan(c, STOP) && 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();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
这段代码的主要逻辑:
1、先判断线程池的状态,如果是ShutDown或者是Stop则直接添加失败。这个我们之前讲过。
2、然后再次判断当前线程的个数是否已经到达了核心线程数,如果到达了,则直接添加失败
3、之后将线程数利用cas的方式进行加1。八、详解CAS无锁-CSDN博客
4、如果上述都满足, 则执行new Worker(firstTask)。 这段代码就是创建一个线程池。我们看一下worker的源码
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
可以看到,Worker的构造函数中,将当前提交的runnable任务复制给firstTask,然后创建了一个线程。并将this复制给线程,因此我们可以推断,Worker类一定实现了Runnable接口,然后创建的这个线程调用start方法的时候,一定运行了Worker的run方法。也就是说线程池中的线程运行逻辑其实就是Worker的run。
那么我们看一下worker的run方法,这个是核心
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);
try {
task.run();
afterExecute(task, null);
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
这段代码,就是线程池中每个线程真正执行的逻辑:
1、第一步就进入一个while循环,判断firstTask是否为空,如果firstTask不为空,则进行循环体,如果为空,则执行getTask(). 这个方法我们一会讲,这个方法里面涉及到了救急线程是如何销毁的。
2、当task不为空的时候,进入循环体,如果为空,调用finaly,然后当前线程结束。这个finnaly里面的逻辑就是将当前worker从workers队列中删除,因为线程销毁了,worker对象也没用用了。然后尝试终止线程池,如果线程池已经没有工作线程,则不在接收新的任务。
3、如果task不为空,也就是能获取到runnable任务,那么就执行runnable的run方法。当执行完之后,将task赋值为null,之后在此进行while循环,从新获取任务,周而复始。
接下来看一下worker中,是如何getTask()的
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();//获取线程池ctl
// Check if queue empty only if necessary.
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, 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 {
//如果线程池超了,那么就利用超时等待的方式区poll,否则就用阻塞的方式。
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
getTask()的核心逻辑就是判断当前线程池中的线程是否大于核心线程数,或者配置核心线程数可销毁。如果是线程数超过了核心线程数,那么本次线程去队列获取任务的时候,就利用等待超时(这里就用到了keeplivetime),如果没有超过,那么就一直阻塞线程,直到有新的任务被添加到队列。
当addWorker执行完之后,将新的worker加入到worker队列中,也就是核心模型中的线程list中。当然这个步骤是加了lock锁的。之后调用worker的start方法,开始执行逻辑。
下面我们回到execute()方法,如果addWorker()失败,那么就向阻塞队列中插入一个任务。
这里用到了阻塞队里的 queue.offer()方法。这里大概看一下阻塞队列是如何工作的,其实就是利用了lock锁和condition。五、详解ReentrantLock-CSDN博客
以LinkedBlockingDeque为例:
public boolean offerLast(E e) {
if (e == null) throw new NullPointerException();
Node node = new Node(e); //创建一个node节点
final ReentrantLock lock = this.lock;
lock.lock(); //加锁
try {
return linkLast(node); //添加元素
} finally {
lock.unlock(); //解锁
}
}
而的关键在于linkLast()方法:
private boolean linkLast(Node node) {
// assert lock.isHeldByCurrentThread();
if (count >= capacity) //超过容量,直接失败
return false;
Node l = last; //获取尾节点
node.prev = l;
last = node;
if (first == null)
first = node;
else
l.next = node;
++count;
notEmpty.signal(); //释放不为空的信号
return true;
}
这里可以看到,当向队列加入元素的时候不会阻塞,如果超过容量则直接返回false。只有从队列拿数据的时候才会阻塞。
继续回到execute()方法,当添加队列失败了,那么就继续判断是否超过核心线程数,如果没有,则创建救急线程。
最后,如果救急线程创建失败,则执行reject方法
这个就是在调用拒绝策略:
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
其实就是调用一开始构造方法里面传递过来的RejectHandler。
接下来分析一下jdk提供的四种拒绝策略:
1、AbortPolicy:直接抛出异常
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
2、CallerRunsPolicy:当前调用线程执行runnable任务。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run(); //当前线程直接调用run
}
}
3、DiscardPolicy:直接丢弃任务
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
//空的方法体,直接忽略这个任务
}
4、DiscardOldestPolicy:丢弃最早添加的那个任务
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll(); //丢弃队列的头
e.execute(r); //重新提交改任务
}
}