线程池原理详解以及源码详解
- 首先先看一下线程池的几个关键参数
corePoolSize 核心线程数
maximumPoolSize最大线程数=核心线程数 + 非核心线程数
keepAliveTime 非核心线程数存活时间
unit 存活时间单位
workQueue 阻塞队列
Executors.defaultThreadFactory() 创建线程池的工厂
defaultHandler 拒绝策略
// runState is stored in the high-order bits 大小一次递减
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
1、RUNNING
(1) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行
处理。
(02) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处
于RUNNING状态,并且线程池中的任务数为0!
2、 SHUTDOWN
(1) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
(2) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
3、STOP
(1) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中
断正在处理的任务。
(2) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or
SHUTDOWN ) -> STOP。
4、TIDYING
(1) 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING
状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在
ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;
可以通过重载terminated()函数来实现。
(2) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也
为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的
任务为空时,就会由STOP -> TIDYING。
5、 TERMINATED
(1) 状态说明:线程池彻底终止,就变成TERMINATED状态。
(2) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -
TERMINATED。
进入TERMINATED的条件如下:
线程池不是RUNNING状态;
线程池状态不是TIDYING状态或TERMINATED状态;
如果线程池状态是SHUTDOWN并且workerQueue为空;
workerCount为0;
设置TIDYING状态成功。
线
线程池的原理图
列举一个线程池max=6,core=3,任务队列taskQueue=5;采用饱和策略为1)
则我们看看提交任务给此线程池的执行逻辑如下:
1)首先我们提交第一个任务到线程池,此时核心线程数都还没有用,所以会启动核心线程之一来执行任务
2)接着提交第二个第三个任务到线程池,他们的执行逻辑同第一个任务是一模一样的,线程池会启动核心线程池中剩下的两个线程来执行你新提交的任务。
3)接着又有新的任务提交过来,这个时候线程池发现核心线程池中的线程已经都在工作中,所以会去看任务队列taskQueue是否满了,发现并没有,是空的,所以将这个任务放入任务队列中等待核心线程池中有空闲线程时自己来取任务执行。
4)接着又提交了4个任务到线程池,他们分别判断核心线程是否空闲,不空闲,然后判断任务队列是否已满,不满,则直接将任务放入队列;
5)接着新的任务又来,则在判断核心线程池和任务队列之后,发现任务依然没有办法处理,(max=6,core=3正在执行任务,新来一个线程,3+1<6,此时队列taskQueue已经满了,corepoolsize=3也都在干活)则会判断是否线程数达到最大,发现没有,则新启动线程来执行任务;(新的任务开始加入到执行任务中,3个corepoolsize+1个非核心线程,此时执行任务4,可理解新添加的任务是非核心线程任务在执行任务)
6)接着又来一个任务,执行流程同5,(此时有5个线程在执行任务,2个非核心线程在执行任务,3个corepoolsize在执行任务);
7)接着又来一个任务,执行流程同5,(此时有6个线程在执行任务,3个非核心线程在执行任务,3个corepoolsize在执行任务);
8)再来一个任务,发现核心线程池在忙,任务队列也满了,线程池中的全部线程也都在工作(3个corepoolsize+3个非核心线程=max(6)都在工作,如果加入新的线程则3个corepoolsize+4个非核心线程>max(6),将报拒绝异常),没有办法处理他了,所以他找到了饱和策略,因为饱和策略是默认的抛异常,所以线程池会告诉提交任务的线程,已经没有可以用的线程了。
以上就一个核心线程数是3,总线程数是6,任务队列长度为5,默认策略采用抛异常的策略的从最开始到最后线程池满负荷运作的过程
线程池源码分析
- 线程池的核心方法就是excute(Runable command),其他的线程池都是最终调用的这个方法的
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();//获取线程池的运行状态和线程数
//1.通过workerCountOf©获取活跃的线程数,如果< corePoolSize, 就调用addWork创建一个核心线程数,只要这里满足,就会一直创建一个新的核心线程,
if (workerCountOf© < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//2. isRunning©判断线程池的运行状态是否为运行态,这种判断在线程池的源码中会经常出现的,只有线程数不小于核心线程数后才会到这来,通过workQueue.offer(command) 把任务放到阻塞队列中
if (isRunning© && workQueue.offer(command)) {
int recheck = ctl.get();
//2.1 不是运行态,并且需要把任务从阻塞队列中移除,就会走拒绝策略,把这个任务废弃掉
if (! isRunning(recheck) && remove(command))
reject(command);
//2.2 如果活跃线程数为0 就需要创建一个非核心线程,去阻塞队列中拿到任务继续执行,这种情况比较少见,比如在1中创建核心线程失败或者1中的addWork()返回false,就有可能会执行这个判断
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//3 队列中放满了并且活跃线程数不小于核心线程数,就会创建非核心线程数,如果addwork()返回false(也就是活跃线程数超过最大线程数了,或者创建非核心线程数失败了),就会执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
2.看了主线了,再看分支,因为创建核心和非核心线程数都是调用的addWork(command) 方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;😉 {
int c = ctl.get();
int rs = runStateOf©;
// Check if queue empty only if necessary
//1. Rs为shutdown及以上的状态时 不再接受新任务了,队列为空也不需要接受任务了,这个判断成立,就返回false,不接收任务了
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
//2. 当前活跃线程数大于线程池承受的最大值就不增加线程或者core为true时大于核心线程数就不添加核心线程,core为false时就不需要添加非核心线程数了
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//3 添加一个线程数,这个只是数量的增加
if (compareAndIncrementWorkerCount©)
break retry;
c = ctl.get(); // Re-read ctl
//如果当前的运行状态不等于上面获取的运行状态,也就是刚才是RUNNING,运行到这里变成其他状态了,这时就重新循环继续修改线程数
if (runStateOf© != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//4这里是把任务放到一个work的对象中,这个work实现了runnable接口,
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());
// rs < SHUTDOWN表示是RUNNING状态;
// 如果rs是RUNNING状态或者rs是SHUTDOWN状态并且firstTask为null,向
线程池中添加线程。
// 因为在SHUTDOWN时不会在添加新的任务,但还是会执行workQueue中的任
务
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) {
//启动线程,这里是启动的work,work中的run方法有一个runWork()方法,runWork这方法中是执行添加的任务的
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
//添加失败就把任务从works中删除
addWorkerFailed(w);
}
return workerStarted;
}
3.Work的run方法中的runWork()
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
//获取第一个任务
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
是否因为异常退出循环
boolean completedAbruptly = true;
try {
创建线程的时候task不为空,第一个任务执行完后就把task置空了,需要通过getTask()从阻塞队列中获取
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 {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
出现异常,回收线程,删除任务
processWorkerExit(w, completedAbruptly);
}
}
-
getTask()从阻塞队列中获取任务以及获取超时
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?for (;😉 {
int c = ctl.get();
int rs = runStateOf©;// 如果线程池的运行状态是shutdown以上并且stop或者队列是空,workCount-1,因为状态是shutdown及以上时,就不允许添加任务了 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); 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 {
//timed 是true时,通过workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS),如果超时获取,就返回null,外层的while退出循环,调用processWorkerExit(w, completedAbruptly);方法把这个线程回收,如果timed是false,通过workQueue.take()从阻塞队列获取任务,如果没有任务就阻塞在这里等待
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
至此整个线程池的流程就结束了
关注点1 线程池大小
线程池有两个线程数的设置,一个为核心池线程数,一个为最大线程数。
在创建了线程池后,默认情况下,线程池中并没有任何线程,等到有任务来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法
当创建的线程数等于 corePoolSize 时,会加入设置的阻塞队列。当队列满时,会创建线程执行任务直到线程池中的数量等于maximumPoolSize。
关注点2 适当的阻塞队列
java.lang.IllegalStateException: Queue full
方法 抛出异常 返回特殊值 一直阻塞 超时退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
检查方法 element() peek() 不可用 不可用
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue: 一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue: 一个不存储元素的阻塞队列。
LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。
关注点3 明确拒绝策略
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。 (默认)
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
说明:Executors 各个方法的弊端:
1)newFixedThreadPool 和 newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。
2)newCachedThreadPool 和 newScheduledThreadPool:
主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM