并发这块相当重要 ,并且初学的时候很难理解是真的,犹记得我在初学并发的时候(当然也不是很久之前),头铁的看了《Java并发编程实战》这本神书。但是由于是刚入门并发这块,所以,读这本书的过程是相当痛苦的,虽然痛苦还是坚持读了下去,虽然一知半解,但总算感觉摸到了门槛。
在看这本书的时候,对线程池这块印象确实比较深,所以决定走一波源码,而且线程池这块也是面试的重点之一吧,去读它的源码对理解线程池很有帮助。
我这里先告知一些应该知道的基本概念:
在看具体的源码之前,照例来看看它继承关系:
一般来说,这个线程池的基石是Executor接口,这个模式基于生产者-消费者模式不过由于它只有一个方法 execute(Runnable runnable),因此实际上常用的是ExecutorService ,它也是一个接口,但是在Executor的基础上增加了许多方法。
ExecutorService 的生命周期有三个状态: 运行、关闭和终止。
下面看看ExecutorService下常用的方法:
public interface ExecutorService extends Executor {
// 平缓的关闭,不再接受新任务,同时等待已经提交给队列的任务完成。
void shutdown();
// 比较粗暴的关闭,会取消所有在运行的任务,那么肯定连队列中的任务也不会再执行。
List<Runnable> shutdownNow();
// 是否关闭。
boolean isShutdown();
// 是否终止。需要注意的是,必须先调用shutdown()和shutdownNow()才会返回true,否则是不会返回true的。
boolean isTerminated();
// 提交Callable的任务。
<T> Future<T> submit(Callable<T> task);
// 提交Runnable的任务,result是做完返回值的。
<T> Future<T> submit(Runnable task, T result);
// 提交Runnable任务。
Future<?> submit(Runnable task);
}
下面再来看看AbstractExecutorService,这里也是只讲一下重要的方法:
public abstract class AbstractExecutorService implements ExecutorService {
// 点进new FutureTask 会看见是先是包装成Callable再赋值给其中的callable变量。
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
// 将Runnable 包装成RunnableFuture(最开始的基本概念已经说了)。
// 再调用上面的newTaskFor方法。
// 下面的大都类似,就不说了。
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
}
下面就是重点对象 ThreadPoolExecutor 类了,它继承自AbstractExecutorService,AbstractExecutorService继承自ExecutorService 。
在看源码之前,先了解线程池的几个状态:
在ThreadPoolExectuor
// COUNT_BITS 为29,为什么是29,因为int总共有32位,线程池用前面3位表示线程池状态,后面29存放线程数
private static final int COUNT_BITS = Integer.SIZE - 3;
// 000 1...,五亿多。
// 表示的是线程池能创建的最大线程数,一般也不会达到这个数量级。
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 线程池的各个状态码 后面29位都是0的
// 111 0...
private static final int RUNNING = -1 << COUNT_BITS;
// 000 0...
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 001 0...
private static final int STOP = 1 << COUNT_BITS;
// 010 0...
private static final int TIDYING = 2 << COUNT_BITS;
// 011 0...
private static final int TERMINATED = 3 << COUNT_BITS;
从上面可以看见,这里注意一下,RUNNING是负数,SHUTDOWN是0,其它的都是大于0的。
然后获取线程池当前的状态和线程数是这样的:
// 将整数 c 的低 29 位修改为 0,就得到了线程池的状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 将整数 c 的高 3 为修改为 0,就得到了线程池中的线程数
private static int workerCountOf(int c) { return c & CAPACITY; }
状态之间的转变如下:
看完了上面的线程池状态讲解,下面正式来读源码,相信看到这里的你已经算是比较有耐心的了。
这次不从变量和构造方法来讲解,而是从常见的新建一个线程池入手。首先,很多人会用Executors这个工具类来创建线程池,一般是这样的:
Executors.newSingleThreadExecutor();
Executors.newFixedThreadPool();
当然还有其他的类型,就不列出来了。当我们点进去看的时候,就会出现下面的:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
// 这个被FinalizableDelegatedExecutorService再次封装,是不允许修改里面的参数的意思。
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
这两个都指向了ThreadPoolExecutor的构造方法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> 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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
上面的这些参数基本上就是线程池的核心变量了,下面讲一下这些变量。
变量 | 作用 |
---|---|
corePoolSize | 核心线程数,一直存活在线程池中,除非allowCoreThreadTimeOut为true,否则即使它们是空闲的, 也不回收。 |
maximumPoolSize | 最大线程数,线程池运行的最大的线程数。与上面CAPACITY是有区别的。 |
keepAliveTime | 存活时间,当线程池中的线程数大于核心线程数,且超过这个时间了还没任务做,那么该线程将会被终止。 |
unit | 存活时间的单位 |
workQueue | 工作队列,在前面基本概念中提了。 |
threadFactory | 线程工厂,顾名思义,就是生产线程的。一般建议用一个带名字的。 |
handler | 饱和策略,一般当线程池中线程数量达到最大线程数,且工作队列也满了的时候,会执行饱和策略。 |
下面看看最核心的execute方法了:
public void execute(Runnable command) {
// 如果提交的任务为null,抛出空指针异常。
if (command == null)
throw new NullPointerException();
// 这个就是上面说的结合 线程池状态和线程数的 整数
int c = ctl.get();
// 获得线程池中的线程数,如果小于核心线程大小。
if (workerCountOf(c) < corePoolSize) {
// 下面会有详细解释,这里大概解释一下。
// 线程池新建一个线程,并将任务提交给这个线程,并成功启动,就会返回true。
// 为false就表示不允许提交任务。
// 如果第二个参数的作用表示界限,为true表示以核心线程数为界限,false则是以最大线程数为界限。
if (addWorker(command, true))
return;
c = ctl.get();
}
// 到这里说明当前线程大小大于核心线程数,或者提交任务失败。
// 如果线程池状态是RUNNING,并且向工作队列添加任务成功。
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 再次检查线程池状态,如果不是RUNNING了,那么执行拒绝策略。
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0) // 如果还是RUNNING,且线程池中没线程。
addWorker(null, false);
}
// 如果不是RUNNING状态,或者工作队列满了,就执行这一步。
// 使用maximumPoolSize为界限再试一次addWorker方法,如果还是失败,那么执行饱和策略
else if (!addWorker(command, false))
reject(command);
}
可能乍一看上去有点绕,梳理一下:
现在看下addWorker(Runnable firstTask, boolean core)方法是如何新建线程的:
// firstTask表示的是任务,core为true绑定的是核心线程大小,为false绑定的是最大线程大小。
private boolean addWorker(Runnable firstTask, boolean core) {
// 这是一个跳出符,类似goto,不知道的可以谷歌一下。
retry:
for (;;) {
int c = ctl.get();
// 获得线程池的状态。
int rs = runStateOf(c);
// 这个if的逻辑可能有点难理解,但是仔细想想也还好,满足下面的条件之一就行。
// 1.线程池的状态大于SHUTDOWN(最开始的时候也说了,RUNNING状态是负数的)。
// 2.如果线程池状态为SHUTDOWN,提交的任务不为null。
// 3.如果线程池状态为SHUTDOWN,提交的任务为null,工作队列为空。
// 这里简单的分析下上面的情况:
// 1.线程池状态大于0,那肯定不是RUNNING和SHUTDOWN状态了吗,return false完全没毛病。
// 这里重复一下,SHUTDOWN状态不接受新任务,但是会处理工作队列中的任务。
// 2.SHUTDOWN状态时,任务不为null,肯定是要return false的。
// 3.任务为null,工作队列也是空的,肯定是返回false的。
// 引申一下,如果状态为SHUTDOWN,工作队列不为null还是会新建线程的,原因看SHUTDOWN的意义。
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
// 到这里说明上面的if判断为false,也就是没有满足上面的条件。
for (;;) {
// 获得当前的线程池中的线程数。
int wc = workerCountOf(c);
// 如果当前线程数大于CAPACITY
// 或者core为true时大于核心大小,为false时大于最大大小,返回false。
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 将那个整数值加一!说白了就是线程数+1,这里使用的是cas,成功就跳出循环,失败就走下面。
if (compareAndIncrementWorkerCount(c))
break retry;
// 再次获得这个整数。
c = ctl.get();
// 如果和最开始的运行状态不一致,那么再从for来一遍。
if (runStateOf(c) != rs)
continue retry;
}
}
// 都到这里了说明一个return都没发生,跳出了for循环,那个整数+1成功了。
// 线程是否开始工作
boolean workerStarted = false;
// 是否有将work添加到workers 中。
boolean workerAdded = false;
Worker w = null;
try {
// 线程池新建一个线程,包装成Worker类。
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());
// 状态为RUNNING或者为SHUTDOWN时,不接受新的任务,处理工作队列,这个上面说过。
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 检查这个新建线程是不是已经启动了,启动了抛异常。
if (t.isAlive())
throw new IllegalThreadStateException();
// 将新建的work添加到workers中,是一个HashSet。
workers.add(w);
// largestPoolSize记录线程池中有过线程数的最大值。
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 如果已经将worker添加到了workers中,启动这个worker。
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
// 如果线程没启动,那么做一些回退动作。
// 将worker从workers去掉,将之前+1的线程数减掉,试着将状态调到TERMINATED。
if (! workerStarted)
addWorkerFailed(w);
}
// 返回线程是否启动成功。
return workerStarted;
}
这个方法看上去也有点绕,但是只要记住其作用核心是新建线程就行了。
这里看下Worker类(只列出变量、构造方法和run方法):
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
final Thread thread;
// 如果新建线程的时候同时指定了任务,那么新建的线程第一个执行这个任务(所以叫firstTask)
// 当然这个任务也可以为null。
Runnable firstTask;
// 存放这个线程完成的任务数量,使用了volatile修饰。
volatile long completedTasks;
// 传入任务,
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
// 使用线程工厂来创建一个线程。
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
}
从Worker源码可以看出,这个类继承自AQS(可以说是无处不在的AQS,并发的基石)。其实从这里可以知道,线程池中的线程都包装成了Worker(译作工人)。话说Doug Lea大神的形容真传神,处理任务(Runnable)的是工人(Worker)。,上面那个addWorker,译作添加工人也完全形象。
我们知道,在addWorker方法中将新建线程启动会调用Worker中的run()方法, 它再调用runWorker(this),那么看一下这个runWorker方法:
// 不断的从队列中获取任务并执行,同时解决一些问题。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// 新建线程传递的那个任务,也可以为null。
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
// 标识符。
boolean completedAbruptly = true;
try {
// 一直调用getTask获取任务去执行。
while (task != null || (task = getTask()) != null) {
w.lock();
// 如果线程池状态是STOP或以上(就是状态值大于STOP的),确保这个线程执行中断。
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) {
// 除了运行时异常的异常不让抛,弄成error抛出去。
thrown = x; throw new Error(x);
} finally {
// 同beforeExecute作用类似,在运行任务结束后执行。
afterExecute(task, thrown);
}
} finally {
task = null;
// 该线程完成的任务数++。
w.completedTasks++;
w.unlock();
}
}
// 到这里说明完整的执行了一个任务,没有抛出异常。
completedAbruptly = false;
} finally {
// 到这里了说明getTask()拿不到任务了或者任务执行的过程中发生异常。
// 如果是拿不到任务了,getTask()自会将workCount - 1 的
// 如果是抛异常,那么执行这个方法将workCount - 1
processWorkerExit(w, completedAbruptly);
}
}
这个方法很好理解,不解释。
下面看看getTask():
// 阻塞直到获取到任务然后返回。
// 也会返回null,返回null的情况为以下几种:
// 1.已经有大于maximumPoolSize的线程数了
// 2.线程池的状态为STOP,或者为SHUTDOWN,且工作队列为空。
// 3.线程等待keepAliveTime 时间了,线程要回收了,
// 但是如果目前线程数小于corePoolSize则不会,除非allowCoreThreadTimeOut设为true。
private Runnable getTask() {
// 表超时。
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 这里可以分为两种情况。
// 1. 状态大于等于STOP。
// 2. 状态为SHUTDOWN,且工作队列为空。
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
// 通过cas将工作线程数-1.
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// 如果allowCoreThreadTimeOut 为true(前面说过,允许回收核心线程数内的线程)
// 或者工作线程数大于核心线程数,那么就会超时关闭了。
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 意图是让工作线程数-1,if只要满足下面条件之一。
// 1. 工作线程大于最大线程数。
// 2. timed && timedOut为true,且 工作线程数大于1或者工作队列为空。
// 讲解一下条件2。
// timed为false时,意味着当前工作线程数小于核心线程数,不管timedOut超时标志是不是true,if都为false。
// 假设timed为true,且timedOut超时标志为true,只要工作线程大于1或者工作队列为空了(回收最后一个线程)
// 核心只要记住一句话。
// 如果allowCoreThreadTimeOut为true,回收所有空闲超时线程,否则,回收大于核心线程数的空闲超时线程。
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
// 到这里就是 工作线程数小于等于maximumPoolSize 且 没有超时
try {
// workQueue 中获取任务
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
// 如果捕获到中断异常,那么重头再来一次。
timedOut = false;
}
}
}
看到这里基本上差不多了。
最后讲一下execute(Runnable command) 方法中可能会执行的reject(command);也就是饱和策略了,这里就不讲具体源码了,带着讲一下:
ThreadPoolExecutor 中有四个已经写好了的饱和策略,一般就用这四个。
- AbortPolicy(中止策略):会抛出未检查的RejectedExecutionException 异常,调用者可以捕获这个异常,然后根据需求编写处理代码。它也是默认的饱和策略。
- DiscardPolicy (抛弃策略):直接抛弃该任务,不做任何处理的。
- DiscardOldestPolicy (抛弃最旧策略):如果线程池状态不为SHUTDOWN,将会抛弃下一个将被执行的任务,然后再把这个任务提交了。(这个操作有点骚气)
- CallerRunsPolicy (调用者运行策略):是一种调节机制,既不会抛弃任务,也不抛出异常, 而是将任务回退给调用者,如,主线程中调用execute方法,如果执行饱和策略后,将该任务的运行提交给主线程去执行,使得一定时间内,主线程无法提交任务。
先看一下newFixedThreadPool,它的作用是创建出固定大小的线程池,因此可以看到它核心线程数和最大线程数是一样的,keepAliveTime 为0,因为最大线程数和核心线程数大小一样,所有这个没必要设置,使用LinkedBlockingQueue有界队列作为工作队列。
这样就可以分析一下,首先在线程池中线程数没有达到nThreads之前,一直是新建线程,达到了就将任务放置在LinkedBlockingQueue队列中,该队列满了就执行饱和策略。如果有线程发生了未预期的异常结束了,那么将补充一个线程。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
newSingleThreadExecutor,它创建只有一个线程的线程池,除了将newFixedThreadPool中的nThreads改为1,其他都一样。
它主要是确保依照任务在队列中的顺序串行执行。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newCachedThreadPool,将创建一个可缓存的线程池,如果当前规模超过了处理需求时,将回收空闲的线程,需求增加时,则添加新线程,没有规模限制(其实还是有的,Integer.MAX_VALUE,不过肯定不会创建这么多线程)。它的工作队列设为SynchronousQueue,其作用在基本概念那里就已经说了。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newScheduledThreadPool,创建一个固定长度的线程池,而且以延迟或定时的方式来执行任务。这个由于没怎么接触过,所以就不多说了。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
哇,经过冗长冗长的一段,终于到了总结这里。我并没有将全部的方法贴过来,毕竟贴完我感觉就要gg了,只是贴了核心部分,但是这也够喝一壶了。其实,并没有什么好总结的…毕竟要说的都在代码里,感情深,一口闷!但还是略微的总结一下吧。
最后,谢谢观看。本人才疏学浅,如有错误之处,欢迎指正,共同进步。