Java并发编程系列文章
欢迎大家观看我的博客,会不断的修正和更新文章,也欢迎大家一起交流
- Java并发编程系列 | 原子操作的底层原理
- Java并发编程系列 | volatile关键字的底层原理
- Java并发编程系列 | synchronized的锁升级过程
- Java并发编程系列 | FutureTask原理详解
- Java并发编程系列 | AQS之ReentrantLock原理详解
- Java并发编程系列 | AQS之条件队列的原理
Java线程池原理详解
写在开头
线程池是非常常用的,在了解tomcat、hystrix的时候,发现都需要去深入的了解一下线程池才好,并且出了大量的框架有用到线程池以外,自己平常的开发也需要大量的使用线程池,那么,我们就先带着一下问题,阅读源代码,去深入的了解一下线程池吧(推荐直接看我写在源代码中的注释)。
- 线程池的运行流程是怎么的?
- keepalivetime是怎么保证当前worker数大于corepoolsize且空闲保活时间时,会减少线程数量
- 它的四种拒绝机制,是什么?什么时候会拒绝?
- 什么时候在哪里创建线程?有什么规则?
- shundown和shutdownnow有啥区别,是怎么让线程池终止的?
- 一个线程是怎么的切换执行多个任务的?
运行流程
首先创建一个线程池以后,那么就从执行任务的时候开始分析,就会开始下面的逻辑
- 如果当前工作线程小于核心线程数量,就直接尝试新增核心线程来执行command
- 核心线程数肯定是满了,加不进去了,尝试把它放到队列里面等待执行,并且需要执行双重检查,在第一次检查之后线程被shutdown了,就要拒绝并移除出队列,尤其是要防止最开始检查时有核心线程,然后检查后核心线程死了,结果放进队列的command得不到执行
- 如果队列也加入不进去了,尝试新增非核心线程来执行,再失败的话就只能采用拒绝策略了。
- 从这里也可以看出来,当线程池有新的任务进来的时候,首先优先是创建核心线程来执行,然后核心线程满了以后就开始使用阻塞队列来存放任务,当阻塞队列也满了的话,就开始创建非核心线程来执行任务了,所以,如果使用的是new LinkedBlockingQueue<>()的话,那么该线程池就不会创建非核心线程,并且无论有多少任务过来,除了shutdown的时候都不会采用拒绝策略拒绝掉。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))//如果当前工作线程小于核心线程数量,就直接新增核心线程来执行command
return;
c = ctl.get();
}
//走到这就说明核心线程数肯定是满了,加不进去了,尝试把它放到队列里面等待执行
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
//如果说在线程死了,这时这个线程池已经没有工作线程了,需要添加工作线程从队列取出command来执行
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))//不能添加进队列,也不能直接新增线程执行的话,采用拒绝策略
reject(command);
}
-
在这里,addWorker内有一段代码的逻辑就是,新增一个工作线程worker,把这个worker加入到该线程池工作线程的集合中,并执行该线程,那么,这里就开始了任务执行的代码了
-
就是在这里创建线程
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
- 在worker中,包含一个线程,在初始化的时候创建,每new一个worker都会创建一个线程,并且,worker本身实现了Runnable接口,相当于把自己当做了一个任务,所以在执行t.start()的时候,启动了worker里的run方法
Worker w = null;
try {
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();
workers.add(w);//加入到该线程池工作线程的集合中
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();//执行该worker工作线程
workerStarted = true;
}
}
}
- worker里的run方法启动后,实际调用的就是runWorker这个方法,而在里面,除了第一次是创建worker时可能会自带task过来以外,其余就一直通过从阻塞队列里面获取任务来执行
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//如果任务为空就从队列里面获取任务,由于是使用阻塞队列的take()方法,队列为空时阻塞
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 {
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 {
// 一般代码执行到这里有三种情况,一种是该线程的任务发生了未捕获的异常,
// 一种是任务队列为空,然后这个worker又在执行超时的策略,超时后也会跑到这里来
// 一种是该线程池被shutdown了,那么从队列取出的任务为空,while条件不满足了
// 就会移除这个worker
processWorkerExit(w, completedAbruptly);
}
}
-
那么,我们可以看一下,getTask()方法,线程池的keepalive和线程池的shundown等,都和这里的实现相关。
-
keepalive是利用阻塞队列的poll()方法设置超时机制,超时后返回为null,再到runworker()方法里面通过processWorkerExit()将worker移除。
-
shundown则是通过在shundown()方法调用inturrupt()方法使得workQueue.take()被打断,再判断为打断状态返回null,到runworker()方法里面去。
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();
//1.如果是shutdownNow()的话,就不用管队列是否为空,直接返回null
//2.如果是shutdown()且队列还有有任务,会先将队列消费完,再返回null
//3. 如果是从workQueue.take()那里被shutdown,就是队列早就是空的,就会for循环运行到这里返回null
return null;
}
int wc = workerCountOf(c);
//如果说允许核心线程超时的话,或者是当前线程个数已经大于核心线程个数了,就允许该线程执行超时的策略
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) : //如果允许超时的话,利用阻塞队列的poll()方法设置超时机制
workQueue.take();//不允许超时的话,那就一直等待,直到shutdown的时候等待状态被interrupt打断
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
- 再就是processWorkerExit()方法了,这个其实看源码方法上的英文注释就差不多了
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 判断是否是因为未捕获的异常引发的需要移除这个worker,因为像keepalive超时到这里来之前已经执行了decrementWorkerCount()方法了
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);//从工作线程集合中移除worker
} 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
}
//如果说这个线程池没有被shutdown,且当前线程数大于最低要求的线程数
// 那么虽然前面移除了worker,这里还是得要再增加一个来替换worker
addWorker(null, false);
}
}
- shutdown使用interruptIdleWorkers来打断线程,队列里面如果还有任务,就会将所有任务执行下去
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {//只有在线程不在执行任务的时候,才会tryLock成功
try {
t.interrupt();//就是说任务队列为空时,线程被阻塞在workQueue.take()时,才会被打断
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
- shutdownNow使用interruptWorkers来打断线程,所以就算这个线程在执行任务时被阻塞依旧会被打断,然后直接执行processWorkerExit()方法
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();//在这里所有的worker是一定会被执行这个方法的
} finally {
mainLock.unlock();
}
}
回答上述的一些问题
- (问题)它的四种拒绝机制,是什么?什么时候会拒绝?
先说总共有2种情况下被拒绝:
1.线程池被shutdown了,这时还往线程池里面添加任务,就会被直接拒绝
2.线程池的最大线程数满了,任务队列也满了,被拒绝
再看看它的四种拒绝策略:
1.AbortPolicy
这是默认的策略,执行的时候会抛出RejectedExecutionException异常
2.CallerRunsPolicy
正如它的英文名字,它的拒绝策略是采用当前调用线程来执行这个任务,而不会去使用线程池里的线程来执行了
3.DiscardOldestPolicy
它会从队列里面取出一个最老的任务,就是队列头部的数据丢弃,再重新调用threadPool.execute
4.DiscardPolicy
抛弃策略会抛弃当前的任务,而不会做任何其他操作 - (问题)一个线程是怎么的切换多个任务执行的?
工作线程每次执行完任务后,都会进行队列的等待,当加入新的任务到队列以后,就会取出来执行,然后再进行等待,再执行,循环往复。 - (问题) 什么时候在哪里创建线程?有什么规则?
每一个worker中都有一个thread字段,在new Worker() 的时候,就会在构造器里将这个线程初始化,并且ThreadPoolExecutor使用HashSet将所有的worker都存了一个集合出来用于管理,创建线程的需要满足的任一条件:
1.当前HashSet中的worker数小于核心线程数
2.核心线程数满了,队列也满了,那就开始创建非核心线程 - (问题)keepalivetime的机制是怎么实现的?
当当前的线程数要大于核心线程数时,一个线程执行完任务后,就会自动通过workQueue.poll() 来获取任务,而keepalive是利用阻塞队列的poll() 方法的参数设置超时机制,超时后直接返回为null,再到runworker() 方法里面通过processWorkerExit() 将worker移除。 - (问题)shundown和shutdownnow有啥区别,是怎么让线程池终止的?
一、shundown则是通过在interruptIdleWorkers() 方法调用inturrupt() 方法使得workQueue.take() 被打断,再判断为打断状态返回null,到runworker() 方法里面去。
这里有几种情况:
1.就是线程阻塞在workQueue.take() 的时候,就是等待从队列中取出任务的时候,是会直接被调用inturrupt() 方法打断的,因为在等队列就说明目前队列中已经没有任务可以执行了,那么就可以直接通过processWorkerExit() 移除这个worker 了。
2.就是interruptIdleWorkers() 执行的时候,这个线程正在执行任务,那么就不能trylock() 成功,它就不会执行inturrupt() 方法,而是继续执行下去,并且在队列中不为空的情况下,会一直从队列里面取出任务来执行,直到队列为空,它就会返回runworker() 里执行processWorkerExit() 将worker移除。已经在getTask() 代码里注释了,看代码会更容易理解。
二、shutdownNow使用interruptWorkers来打断线程,所以就算这个线程在执行任务时被阻塞依旧会被打断,然后直接执行processWorkerExit()方法,并且即使这个线程在任务里面被阻塞,也会直接引起中断异常,并且任务执行完以后,不论队列有没有任务,都会直接执行processWorkerExit() 将worker移除。