为什么要使用线程池
线程池应该是大家耳熟能详的一个东西,它是juc包下比较简单的一个组件,也是我们最常用的一个组件,其作用就是帮我们管理线程,让线程能并发执行多个任务的同时,不造成很大的系统开销。
熟悉java并发编程的同学应该都知道,常见的多线程执行任务,只要 new 一个 Thread 出来让它跑就行了,看上去是一个很简单的动作,那还要线程池来干吗呢
总体来说,如果不使用线程池,会有以下两个问题。
-
频繁创建线程,造成系统负荷较大。
其实 Java 中的线程模型是基于操作系统原生线程模型实现的,也就是说 Java 中的线程其实是基于内核线程实现的。频繁创建线程,线程的生命周期包括线程创建、线程销毁,都需要操作系统的调用。
-
频繁创建线程,过多消耗内核资源
每个 Thread 都需要有一个内核线程的支持,也就意味着每个 Thread 都需要消耗一定的内核资源(如内核线程的栈空间)。如果无限制地创建线程,很容易造成系统崩溃。
而用了线程池,把线程的使用“池化”,就可以很好地解决上面2个问题。
那线程是怎么池化的呢?换句话说,线程是如何做到执行完一个任务以后,还继续存活的呢?(正常来说,一条线程做完一个runnable任务就消亡了)后续的内容会揭晓这个问题的答案。
线程的生命周期
NEW,新建
RUNNABLE,运行
BLOCKED,阻塞
WAITING,等待
TIMED_WAITING,超时等待
TERMINATED,终结
一点说明:等待状态和阻塞状态有什么不同?可以这么理解,进入等待状态是线程主动的,而进入阻塞状态是被动的。
根据上图,总结一下java常用的API对线程的影响
操作命令 | 执行命令后线程的状态 | 是否让出CPU使用权 | 是否阻塞 | CPU是否能调度该线程 |
Thread.yield | 就绪 | 是 | 否 | 是 |
Thread.sleep | 运行中 | 否 | 否 | 是 |
LockSupport.park | 等待状态 | 是 | 是 | 否 |
Object.wait | 等待状态 | 是 | 是 | 否 |
Thread.join | 等待状态 | 是 | 是 | 否 |
synchronized | 阻塞 | 是 | 是 | 否 |
这里需要注意一点,sleep虽然不会让出cpu的使用权,但是有可能在sleep时,操作系统调度时间片已经轮到了其他线程执行了。这个时候,sleep的线程还是会让出cpu使用权的(但是这个不是sleep导致的,而是操作系统本身的调度机制导致的)
线程池的生命周期
1.线程池重点属性——ctl
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
(1)ctl 是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段, 它包含两部分的信息: 线程池的运行状态 (runState) 和线程池内有效线程的数量(workerCount),高3位保存runState,低29位保存workerCount。
(2)相关方法
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; } // 获取运行状态和活动线程数的值。
2.线程池状态图
RUNNING = -1 << COUNT_BITS; //高3位为111
// 线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
// 程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0
----------
SHUTDOWN = 0 << COUNT_BITS; //高3位为000
// 线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。(调用线程池的shutdown()接口)
----------
STOP = 1 << COUNT_BITS; //高3位为001
// 线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务(调用线程池的shutdownNow()接口)
----------
TIDYING = 2 << COUNT_BITS; //高3位为010,
// 当所有的任务已终止,ctl记录的“任务数量”为0,线程池会变为TIDYING状态。
// 线程池变为TIDYING状态时,会执行钩子函数terminated()(需要用户重载实现)
// 当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
// 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
----------
TERMINATED = 3 << COUNT_BITS; //高3位为011
// 线程池彻底终止,就变成TERMINATED状态。
// 线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
// 进入TERMINATED的条件如下:
1.线程池不是RUNNING状态;
2.线程池状态不是TIDYING状态或TERMINATED状态;
3.如果线程池状态是SHUTDOWN并且workerQueue为空;
4.workerCount为0;
5.设置TIDYING状态成功。
这里的重点是调用shutdown和shutdownNow的区别
线程池的核心参数详解
1.构造方法核心参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue);
(1)corePoolSize:核心线程数
(2)maximumPoolSize:最大线程数(包含核心线程)
(3)keepAliveTime:每个线程保持活着的空闲时间数字,这个一般对非核心线程才有效。当打开开关以后才会对核心线程有效
(4)unit:keepAliveTime的时间单位
(5)workQueue:存放线程的队列(线程超过corePoolSize时用到),在JDK中提供了如下阻塞队列
a.ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
b.LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务 ;
c.SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;
d.priorityBlockingQuene:具有优先级的无界阻塞队列;
(6)threadFactory:线程工厂,主要用来创建线程。默认使用Executors.defaultThreadFactory() 来创建线。使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称。
(7)handler:线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略
-
AbortPolicy:直接抛出异常,默认策略;
-
CallerRunsPolicy:用调用者所在的线程来执行任务;(即异步变成同步来处理)
-
DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
-
DiscardPolicy:直接丢弃任务;
上面都是ThreadPoolExecutor的内部类。如果觉得4种策略都不满足使用者的需求,可以自己定义拒绝策略,实RejectedExecutionHandler接口
2.线程池执行步骤分析
线程池执行的核心流程
1.线程池执行入口分析
//1.0 线程池执行入口
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 1.1 正在使用的线程数如果小于核心线程数
if (workerCountOf(c) < corePoolSize) {
// 1.2 直接增加一条核心线程,然后执行(true表示以核心线程的工作模式进行工作)
// 2.0 整个execute流程,addWorker这个地方是核心,这里的逻辑决定了整个线程池是怎么工作的,进去看看逻辑
// 注意,这里我们只是把Runnable传进去了,而Runnable的执行是依赖Thread的,但是我们这里并没有传Thread
if (addWorker(command, true))
return;
c = ctl.get();
}
// 1.3 如果超过了核心线程数了,请求进入队列,等待核心线程执行完了以后,从队列里获取任务执行
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
// 1.4 这里处理的情况是,有可能1.1步骤判断核心线程已经足够了,但是执行到1.4的时候,核心线程等待全部超时了
// 这里就要重新创建一个工人去队列里拿任务,但是要注意,task必须为null,表示是直接从队列里拿任务
// 这里不理解没关系,等会讲完整个流程就会明白了
addWorker(null, false);
}
// 1.5 入队失败(队列满了),根据线程池的最大线程数配置,额外再创建一条线程进行任务处理(false表示以非核心线程的工作模式工作)
else if (!addWorker(command, false))
// 1.6(end) 最大线程数配置也满了,则任务执行失败,执行拒绝策略
reject(command);
}
2.Worker类和addWorker方法分析
private boolean addWorker(Runnable firstTask, boolean core) {
// 2.1 这段是校验逻辑,可以不用过多关注
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 w = null;
try {
//2.2 这里的firstTask其实就是我们传进来的Runnable对象
//这里可以看到,其实我们是把Runnable对象Worker里去了
//然后可以看到,Worker对象里有一个Thread线程对象
//这里我们可以这样理解worker——它是线程池执行任务的承载类,里面存在着我们的任务和执行任务的线程
//2.4 进入Worker类,看具体的执行逻辑
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 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) {
//2.3 线程开始执行我们的任务,具体怎么执行的,要进到Worker类里看逻辑
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
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;
//2.5 其实这里执行的线程就是一条普通的线程
//可以看到,线程执行的Runnable就是把Worker的本身传进去了(Worker是实现了Runnable接口的)
//所以,this.thread执行start方法的时候,其实就是执行了Worker的run方法
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
//2.6 Worker类的run方法,继续进入runWorker
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
//2.7 这里的task,其实就是我们最开始传进来的Runnable
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//2.8 第一次调用的时候,task肯定是不为null的,所以直接进入逻辑
//3.0 在2.10中,已经把task置为了null,所以这里的第二次循环,主要看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 {
beforeExecute(wt, task);
Throwable thrown = null;
try {
//2.9 真正执行我们的任务
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 {
//2.10(end) 执行的任务置为null
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
3.getTask()分析
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;
}
//3.1 计算当前工作中的线程数有多少
int wc = workerCountOf(c);
//3.2 判断这条线程是否允许过期——2种情况,一是配置了核心线程数允许过期,二是这条线程在目前情况下是非核心线程
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//3.3 校验的代码先忽略
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//3.4 重点在这里,当这条线程会拉取线程队列的线程
//如果允许过期,就用带过期时间的API
//如果不允许过期,则直接一直阻塞等待
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
//3.5(end) 拿到了任务,就把这个任务返回去执行
//(继续分析第2个任务和后续任务来了以后,线程池是怎么处理的)
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
4.线程池执行时,抛异常分析
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 {
//4.0 如果这里我们抛了异常怎么办?
//可以看到下面代码,虽然把异常catch住了,但是又throw出去了
//难道抛了异常我们就不管了吗?当然不是
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 {
//2.10(end) 执行的任务置为null
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//4.1 如果抛了异常,代码最后会执行到这里(当然,没抛异常也会执行到这里,区别就是completedAbruptly的取值问题)
//并且分析逻辑,可以发现这里的completedAbruptly=true(表示是被异常打断的)
//这里要明确一点,抛了异常以后(其实不管是否抛异常都是),worker里的thread在执行完finally方法以后,就消亡了
//这里进去processWorkerExit看看
processWorkerExit(w, completedAbruptly);
}
}
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//4.2 completedAbruptly传过来的是true,所以会执行逻辑
//这里的执行具体逻辑就是worker减1(因为刚才说了,worker里的thread会消亡,这里worker没存在的意义了)
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)) {
//4.3 判断线程池的运行状态,进入到这里表示线程池还在运行
//completedAbruptly我们传进来的是true,不符合逻辑,跳过
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
//4.4(end) 增加1个任务为空的worker(但是执行的线程是有的),意思是直接从队列里拿任务执行
//所以这里明白1.4里的逻辑了吗
//(总结整个线程池的运作机制)
addWorker(null, false);
}
}
5.流程总结
(1)首先,线程池创建后,里面是没有线程的(可以通过预热改变这个设置)
(2)第1个请求来执行任务,线程池会创建一条线程来执行,执行完以后,这条线程会一直阻塞等待阻塞队列有新的任务到来(可以通过开关和配置时间来配置阻塞等待的时长,如果不配置,则一直阻塞等待)
(3)假设第1个请求执行完了之后,第2个请求才来请求执行,线程池不会拿之前创建的线程来执行这个请求,而是创建1条新的线程来执行(假设核心线程数大于1),执行完以后,一样会一直阻塞等待阻塞队列有新的任务到来
(4)持续(3)这种情况,一直到等待阻塞队列线程的数量到达核心线程数
(5)所有的核心线程数都已经创建完毕后,后面所有的请求都会先打到阻塞队列,让核心线程一直去消费队列的元素
(6)如果核心线程消费很慢,任务提交的速度大大超过了任务处理的速度,把阻塞队列都塞满了,则新来任务的时候,会先额外创建非核心线程来应急处理
(7)非核心线程处理完以后,不会立刻消亡,而是也会去阻塞队列去拉任务,阻塞最大的时间就是线程池构造方法的那个时间
(8)如果非核心线程数+核心线程已经到达最大线程数,也还是无法满足当前处理速度的需求,则会执行拒绝策略
6.setCorePoolSize()方法分析
//5.0 线程池提供了很多修改参数配置的方法,最典型的就是这个修改核心线程数
//修改核心线程数涉及到一个比较麻烦的问题
//前面我们提到,线程在执行完任务以后,会继续阻塞等待
//如果我们把核心线程数修改少了,但是线程却在阻塞等待任务,怎么办?
public void setCorePoolSize(int corePoolSize) {
if (corePoolSize < 0)
throw new IllegalArgumentException();
//5.1 计算线程数是增大还是减少
int delta = corePoolSize - this.corePoolSize;
this.corePoolSize = corePoolSize;
//5.4 如果当前的核心线程数变小
if (workerCountOf(ctl.get()) > corePoolSize)
//5.5 中断在等待中的线程,具体怎么中断的,可以进去看看
interruptIdleWorkers();
//5.2 先看增大的情况,比较简单
else if (delta > 0) {
int k = Math.min(delta, workQueue.size());
//5.3 直接循环addWorker就行了,其实就是增加多几个线程在阻塞等待队列
while (k-- > 0 && addWorker(null, true)) {
if (workQueue.isEmpty())
break;
}
}
}
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
//5.6 如果加锁成功,表示线程没有在执行任务,进行中断
//中断以后,等到中的线程就会抛中断异常
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
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);
//5.9 如果核心线程数变小了,这条线程就算之前是核心线程,这里也有可能会变成非核心线程
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//3.3 校验的代码先忽略
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//5.7 这里如果被中断了,就会抛中断异常,被catch住了
//5.10(end) 然后这里就按照非核心线程的逻辑进行操作
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
//5.8 获取任务失败了,但是不是因为超时失败的,继续执行循环
timedOut = false;
}
}
}
使用线程池时,遇到的坑
看下面一段代码
import java.util.concurrent.*;
public class ThreadPoolTest {
private static ThreadPoolExecutor executor =
new ThreadPoolExecutor(1,1,10, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(256));
public static void main(String[] args) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
method();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
private static void method() throws ExecutionException, InterruptedException {
Future<Object> submit = executor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
return new Object();
}
});
Object o = submit.get();
}
}
上面代码有什么问题?分析一下流程:
(1)第1个任务提交,会把线程池唯一存在的一条线程占用住了
(2)执行到method方法,又提交了一个任务,这个任务比较特别,是我们需要用到返回值的任务,线程池会一直等待任务执行完成
(3)但是前面说到,线程池存在的唯一一条线程被第1个任务占用了,所以第2个任务只能进入队列
(4)而第1个任务因为第2个任务没返回,所以这条线程一直不能释放
(5)至此,问题产生了——第1个任务需要第2个任务的返回值、第2个任务的执行又需要第1个任务释放线程。而后面所有的请求过来,都只有堵在那里了
解决这个问题的方案也比较简单,就是尽量避免共用线程池