文章目录
Java线程池原理及其执行过程源码分析
1. 为什么要使用线程池
有三个优点:
- 减少了线程的创建和销毁次数,节省了CPU资源,线程池中的线程能够复用
- 便于线程的管理,可以对线程进行统一管理
- 能够保证线程不无限的增加。控制并发数量
2. 线程池的原理
Java中的线程池顶层接⼝是 Executor
接⼝, ThreadPoolExecutor
是这个接⼝的实现类。
我们先看看 ThreadPoolExecutor
类。
2.1 ThreadPoolExecutor 的构造方法
// 五个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
// 六个参数的构造函数-1
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
// 六个参数的构造函数-2
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
// 七个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
涉及到5~7个参数,我们先看看必须的5个参数是什么意思:
int corePoolSize:该线程池中核⼼线程数最⼤值
核⼼线程:线程池中有两类线程,核⼼线程和⾮核⼼线程。核⼼线程默
认情况下会⼀直存在于线程池中,即使这个核⼼线程什么都不⼲(铁饭
碗),⽽⾮核⼼线程如果⻓时间的闲置,就会被销毁(临时⼯)。
int maximumPoolSize:该线程池中线程总数最⼤值 。
该值等于核⼼线程数量 + ⾮核⼼线程数量。
long keepAliveTime:⾮核⼼线程闲置超时时⻓。
⾮核⼼线程如果处于闲置状态超过该值,就会被销毁。如果设置
allowCoreThreadTimeOut(true),则会也作⽤于核⼼线程。
TimeUnit unit:keepAliveTime的单位。
TimeUnit是⼀个枚举类型 ,包括以下属性:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000 MICROSECONDS : 1微秒 =
1毫秒 / 1000 MILLISECONDS : 1毫秒 = 1秒 /1000 SECONDS : 秒
MINUTES : 分 HOURS : ⼩时 DAYS : 天
BlockingQueue workQueue:阻塞队列,维护着等待执⾏的Runnable任务对象。
常⽤的⼏个阻塞队列:
1. LinkedBlockingQueue
链式阻塞队列,底层数据结构是链表,默认⼤⼩是 Integer.MAX_VALUE ,
也可以指定⼤⼩。
2. ArrayBlockingQueue
数组阻塞队列,底层数据结构是数组,需要指定队列的⼤⼩。
3. SynchronousQueue
同步队列,内部容量为0,每个put操作必须等待⼀个take操作,反之亦
然。
4. DelayQueue
延迟队列,该队列中的元素只有当其指定的延迟时间到了,才能够从队列
中获取到该元素。
还有两个⾮必须的参数。
ThreadFactory threadFactory 创建线程的⼯⼚ ,⽤于批量创建线程,统⼀在创建线程时设置⼀些参数,如是否守护线程、线程的优先级等。如果不指定,会新建⼀个默认的线程⼯⼚。
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
RejectedExecutionHandler handler拒绝处理策略,线程数量⼤于最⼤线程数就会采⽤拒绝处理策略,四种拒绝处理的策略为 :
- ThreadPoolExecutor.AbortPolicy:默认拒绝处理策略,丢弃任务并抛
出RejectedExecutionException异常。 - ThreadPoolExecutor.DiscardPolicy:丢弃新来的任务,但是不抛出异
常。 - ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列头部(最旧的)
的任务,然后重新尝试执⾏程序(如果再次失败,重复此过程)。 - ThreadPoolExecutor.CallerRunsPolicy:由调⽤线程处理该任务
2.2 ThreadPoolExecutor的策略
线程池本身有⼀个调度线程,这个线程就是⽤于管理布控整个线程池⾥的各种任务和事务,例如创建线程、销毁线程、任务队列管理、线程队列管理等等。
故线程池也有⾃⼰的状态。 ThreadPoolExecutor 类中定义了⼀个 volatile int 变量runState来表示线程池的状态 ,分别为RUNNING、SHURDOWN、STOP、TIDYING 、TERMINATED。
-
线程池创建后处于RUNNING状态。
-
调⽤shutdown()⽅法后处于SHUTDOWN状态,线程池不能接受新的任务,清除⼀些空闲worker,会等待阻塞队列的任务完成。
-
调⽤shutdownNow()⽅法后处于STOP状态,线程池不能接受新的任务,中断所有线程,阻塞队列中没有被执⾏的任务全部丢弃。此时,poolsize=0,阻塞队列的size也为0。
-
当所有的任务已终⽌,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。接着会执⾏terminated()函数。
ThreadPoolExecutor中有⼀个控制状态的属性叫ctl,它是⼀个AtomicInteger类型的变量。
- 线程池处在TIDYING状态时,执⾏完terminated()⽅法之后,就会由 TIDYING-> TERMINATED, 线程池被设置为TERMINATED状态。
2.3 线程池主要的任务处理流程
处理任务的核⼼⽅法是 execute ,我们看看 JDK 1.8 源码中 ThreadPoolExecutor 是
如何处理线程任务的:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
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);
}
查看官网的源代码,我们就能通过官网的注释明白线程池的主要工作原理。传入一个 Runable 实现类(Lamdba表达式)就能够通过线程池来进行执行。我们就站在官网的角度来分析这个代码。
- 第一步大体意思就是当线程池中的 corePoolSize 数量要小于正在运行的worker线程的数量时,就将此任务做为第一个任务创建一个新的核心线程。调用 addWorker() 方法
addWorker()
private boolean addWorker(Runnable firstTask, boolean core) {
// 源代码的第一部分
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);
// 对当前的woker数量进行判断,是否大于容量 core 为true 表示创建核心线程,
// 反之为非核心线程
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// CAS 来增加WorkCount 的值
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 {
// 实例化一个工作线程,将第一个任务传入进去
w = new Worker(firstTask);
// w.thread 就是工作线程
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.
// 拿着锁重新检查。在ThreadFactory出现故障或在获得锁之前关闭时退出。
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 内部使用一个 hashset来进行存储工作线程
workers.add(w);
// 得到hashset 的数量
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;
虽然代码有一点长,但是并不难理解,代码的第一部分的主要逻辑就是判断线程池中的核心线程是否已经满了,以及对线程池状态的判断。如果都满足,就使用CAS来增加核心线程数量这个变量。然后进入第二部分的代码,第二部分的代码其实也比较简单,就是实例化一个Worker线程,并且启动这个线程,这里面还有一个步骤就是统计当前所有工作线程的数量,对largestPoolSize 这个值进行更新,如果超过线程池中最大核心线程数,就会根据定时来对额外的工作线程进行销毁。为了更好的理解,我们来看一个 Worker类
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
// 将要执行的线程赋值给 firstTask
this.firstTask = firstTask;
// 自己实例化一个线程,作为工作线程。
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
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) {
}
}
}
}
Worker 类实现了 Runnable 接⼝,所以 Worker 也是⼀个线程任务。在构造⽅法中,创建了⼀个线程,线程的任务就是⾃⼰。故 addWorker ⽅法调⽤addWorker⽅法源码下半部分中的第4步 t.start ,会触发 Worker 类的 run ⽅法被JVM调⽤。
分析完Work线程的创建过程之后,我们来看一下这个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 {
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 {
// 这里使用的是run 方法,而不是start ,相当于直接用让jvm来调用
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);
}
}
⾸先去执⾏创建这个worker时就有的任务,当执⾏完这个任务后,worker的⽣命周期并没有结束,在 while 循环中,worker会不断地调⽤ getTask ⽅法从阻塞队列中获取任务然后调⽤ task.run() 执⾏任务,从⽽达到复⽤线程的⽬的。只要 getTask ⽅法不返回 null ,此线程就不会退出。当然,核⼼线程池中创建的线程想要拿到阻塞队列中的任务,先要判断线程池的状态,如果STOP或者TERMINATED,返回 null 。所以当使用showdown方法时,会将没有执行完的线程执行完,然后才不会继续执行。这里还有需要注意的地方就是 beforeExecute
方法和 afterExecute
可以通过继承这个类,对这两个方法重写,实现对线程任务的加强(类似于AOP)
最后,我们来看 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;
}
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 {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
核⼼线程的会⼀直卡在 workQueue.take ⽅法,被阻塞并挂起,不会占⽤CPU资源,直到拿到 Runnable 然后返回(当然如果allowCoreThreadTimeOut设置为 true ,那么核⼼线程就会去调⽤ poll ⽅法,因为 poll 可能会返回 null ,所以这时候核⼼线程满⾜超时条件也会被销毁)。⾮核⼼线程会workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) ,如果超时还没有拿到,下⼀次循环判断compareAndDecrementWorkerCount就会返回 null ,Worker对象的 run() ⽅法循环体的判断为 null ,任务结束,然后线程被系统回收 。
整体流程就是:
2.4 ThreadPoolExecutor如何做到线程复⽤的?
如果看完了上面的源码分析,就能够知道,当我们将一个线程传入到线程池中时,线程池中的线程执行任务是直接调用我们传入线程的run() 方法,就相当于调用了一个普通方法,如果核心线程发现任务队列没有线程,就会阻塞直到任务队列中有线程
3. 四种常见线程池
Executors 类中提供的⼏个静态⽅法来创建线程池。⼤家到了这⼀步,如果看懂了前⾯讲的 ThreadPoolExecutor 构造⽅法中各种参数的意义,那么⼀看
到 Executors 类中提供的线程池的源码就应该知道这个线程池是⼲嘛的。
3.1 newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
CacheThreadPool 的运⾏流程如下:
- 提交任务进线程池。
- 因为corePoolSize为0的关系,不创建核⼼线程,线程池最⼤为Integer.MAX_VALUE。
- 尝试将任务添加到SynchronousQueue队列。
- 如果SynchronousQueue⼊列成功,等待被当前运⾏的线程空闲后拉取执行。如果当前没有空闲线程,那么就创建⼀个⾮核⼼线程,然后从SynchronousQueue拉取任务并在当前线程执⾏。
- 如果SynchronousQueue已有任务在等待,⼊列操作将会阻塞。
当需要执⾏很多短时间的任务时,CacheThreadPool的线程复⽤率⽐较⾼,会显著的提⾼性能。⽽且线程60s后会回收,意味着即使没有任务进来,CacheThreadPool并不会占⽤很多资源。
3.2 newFixedThreadPool
public static ExecutorService newFixedThreadPool(intnThreads){
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
核⼼线程数量和总线程数量相等,都是传⼊的参数nThreads,所以只能创建核⼼线程,不能创建⾮核⼼线程。因为LinkedBlockingQueue的默认⼤⼩是Integer.MAX_VALUE,故如果核⼼线程空闲,则交给核⼼线程处理;如果核⼼线程不空闲,则⼊列等待,直到核⼼线程空闲。
与CachedThreadPool
的区别:
- 因为
corePoolSize == maximumPoolSize
,所以FixedThreadPool只会创建核⼼线程。 ⽽CachedThreadPoo
l因为corePoolSize=0,
所以只会创建⾮核⼼线程。 - 在 getTask() ⽅法,如果队列⾥没有任务可取,线程会⼀直阻塞在
LinkedBlockingQueue.take()
,线程不会被回收。 CachedThreadPool会在60s后收回。 - 由于线程不会被回收,会⼀直卡在阻塞,所以没有任务的情况下,
FixedThreadPool
占⽤资源更多。 - 都⼏乎不会触发拒绝策略,但是原理不同。
FixedThreadPool
是因为阻塞队列可以很⼤(最⼤为Integer最⼤值),故⼏乎不会触发拒绝策略;CachedThreadPool
是因为线程池很⼤(最⼤为Integer最⼤值),⼏乎不会导致线程数量⼤于最⼤线程数,故⼏乎不会触发拒绝策略。
3.3 newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
有且仅有⼀个核⼼线程( corePoolSize == maximumPoolSize=1),使⽤了LinkedBlockingQueue(容量很⼤),所以,不会创建⾮核⼼线程。所有任务按照先来先执⾏的顺序执⾏。如果这个唯⼀的线程不空闲,那么新来的任务会存储在任务队列⾥等待执⾏。
3.4 newScheduledThreadPool
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
这个线程池主要作用就是支持定时以及周期性执行任务。