1. 什么是线程池
线程池:管理一组工作线程的资源池。
2. 为什么使用线程池
1.避免反复创建回收线程,降低资源消耗。
2.提供线程的可管理性。
3.提高响应速度
3. 如何创建线程池
ThreadPoolExecutor是jdk提供的线程池的服务类,基于ThreadPoolExecutor可以很容易将一个实现Runnable接口的任务放入线程池中执行,下面是ThreadPoolExecutor实现:
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:线程池中线程的个数。
maximumPoolSize:线程池最大容量。
keepAliveTime:线程池中空闲存活时间(单位:TimeUnit定)
workQueue:存放任务的工作队列。
threadFactory:线程工厂
handler:当任务数超过maximumPoolSize+corePoolSize后,任务交给handler处理。
3.1. corePoolSize
线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。这里需要注意的是:在刚刚创建ThreadPoolExecutor的时候,线程并不会立即启动,而是要等到有任务提交时才会启动,除非调用了prestartCoreThread/prestartAllCoreThreads事先启动核心线程。再考虑到keepAliveTime和allowCoreThreadTimeOut超时参数的影响,所以没有任务需要执行的时候,线程池的大小不一定是corePoolSize。
3.2. maximumPoolSize
线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务。这里值得一提的是largestPoolSize,该变量记录了线程池在整个生命周期中曾经出现的最大线程个数。为什么说是曾经呢?因为线程池创建之后,可以调用setMaximumPoolSize()改变运行的最大线程的数目。
3.3. keepAliveTime
线程池中超过corePoolSize的线程会在空闲keepAliveTime时间后被关闭,keepAliveTime单位由TimeUnit指定,如果allowCoreThreadTimeOut=true,即核心线程如果空闲时间超过keepAliveTime时间后同样会被关闭。
3.4. workQueue
当核心线程池已满,即当前worker数=corePoolSize时,新提交的任务会被放入工作队列中。此时一旦有worker完成手头的任务就会到workQueue中领取一个新任务继续执行。
工作队列可以有以下几种选择:
(1).ArrayBlockingQueue:基于数组的有界阻塞队列
(2).LinkedBlockingQueue:基于链表的阻塞队列,可以不指定队列大小,默认Integer.MAX_VALUE。性能高于ArrayBlockingQueue。
(3).SynchronousQueue:“是这样 一种阻塞队列,其中每个 put 必须等待一个 take,反之亦然。同步队列没有任何内部容量。翻译一下:这是一个内部没有任何容量的阻塞队列,任何一次插入操作的元素都要等待相对的删除/读取操作,否则进行插入操作的线程就要一直等待,反之亦然。
SynchronousQueue<Object> queue = new SynchronousQueue<Object>();
// 不要使用add,因为这个队列内部没有任何容量,所以会抛出异常“IllegalStateException”
// queue.add(new Object());
// 操作线程会在这里被阻塞,直到有其他操作线程取走这个对象
queue.put(new Object());
(4).PriorityBlockingQueue:具有优先级的阻塞列,基于数组实现,内部实际上实现了一个最小堆,每次offer、poll,都需要进行堆调整操作O(logn)。队列中元素需要实现Comparable接口或初始化队列时传入一个Comparator对象。虽然初始化队列时需指定队列大小,但PrioriityBlockingQueue支持动态扩容,所以可以认为是无限阻塞队列。
3.5 ThreadFactory
线程池最主要的一项工作,就是在满足某些条件的情况下创建线程。而在ThreadPoolExecutor线程池中,创建线程的工作交给ThreadFactory来完成。要使用线程池,就必须要指定ThreadFactory。
类似于上文中,如果我们使用的构造函数时并没有指定使用的ThreadFactory,这个时候ThreadPoolExecutor会使用一个默认的ThreadFactory:DefaultThreadFactory。(这个类在Executors工具类中)
3.6 拒绝任务(handler)
在ThreadPoolExecutor线程池中还有一个重要的接口:RejectedExecutionHandler。当提交给线程池的某一个新任务无法直接被线程池中“核心线程”直接处理,又无法加入等待队列,也无法创建新的线程执行;又或者线程池已经调用shutdown()方法停止了工作;又或者线程池不是处于正常的工作状态;这时候ThreadPoolExecutor线程池会拒绝处理这个任务,触发创建ThreadPoolExecutor线程池时定义的RejectedExecutionHandler接口的实现
在ThreadPoolExecutor中已经提供了四种可以直接使用的RejectedExecutionHandler接口的实现:
3.6.1 CallerRunsPolicy:
这个拒绝处理器,将直接运行这个任务的run方法。但是,请注意并不是在ThreadPoolExecutor线程池中的线程中运行,而是直接调用这个任务实现的run方法。源代码如下:
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { }
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
3.6.2 AbortPolicy:
这个处理器,在任务被拒绝后会创建一个RejectedExecutionException异常并抛出。这个处理过程也是ThreadPoolExecutor线程池默认的RejectedExecutionHandler实现。
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
3.6.3 DiscardPolicy:
DiscardPolicy处理器,将会默默丢弃这个被拒绝的任务,不会抛出异常,也不会通过其他方式执行这个任务的任何一个方法,更不会出现任何的日志提示。
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
3.6.4 DiscardOldestPolicy:
这个处理器很有意思。它会检查当前ThreadPoolExecutor线程池的等待队列。并调用队列的poll()方法,将当前处于等待队列列头的等待任务强行取出,然后再试图将当前被拒绝的任务提交到线程池执行:
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardOldestPolicy} for the given executor.
*/
public DiscardOldestPolicy() { }
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
实际上查阅这四种ThreadPoolExecutor线程池自带的拒绝处理器实现,您可以发现CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy处理器针对被拒绝的任务并不是一个很好的处理方式。
CallerRunsPolicy在非线程池以外直接调用任务的run方法,可能会造成线程安全上的问题;DiscardPolicy默默的忽略掉被拒绝任务,也没有输出日志或者提示,开发人员不会知道线程池的处理过程出现了错误;DiscardOldestPolicy中e.getQueue().poll()的方式好像是科学的,但是如果等待队列出现了容量问题,大多数情况下就是这个线程池的代码出现了BUG。最科学的的还是AbortPolicy提供的处理方式:抛出异常,由开发人员进行处理。
4. 创建线程池
jdk为我们提供了一个工厂类Executors,其中提供了几个静态工厂方法用于新建不同特性的线程池。如下:
public class Executors {
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
newFixedThreadPool:将创建一个固定长度的线程池,每提交一个任务时就创建一个线程,直到达到线程池的最大数量,此时线程池的规模不再变化(如果某个线程发生Exception而结束,那么线程池会补充一个新的线程)
newCachedThreadPool:创建一个可缓存的线程池,如果当前线程池中线程的个数超过了处理需求时,那么空闲线程将被回收,而当需要增加线程时,则可以添加新的线程,线程池中个数不受限制(使用时格外注意,防止内存溢出)
newSingleThreadPool:这是一个单线程的Executor,它创建单个工作线程来执行任务,如果线程异常结束,会创建一个新的线程来替代。newSingleThreadPool能确保依照任务在队列中的顺序串行执行。
newScheduledThreadPool:创建一个固定长度的线程池,而且可以延时或定时方式来执行任务。
注意事项
1.newFixedThreadPool和newSingleThreadExecutor都是用了LinkedBlockingQueue(),默认capacity=Integer.MAX_VALUE,线程池工作队列可以认为是无限大的,所以线程池中的线程数不会超过CorePoolSize,maximumPoolSize可以认为是一个无效参数,且饱和策略不可能执行,这几点需要注意。
2.newFixedThreadPool(1)和newSingleThreadPool区别?
newSingleThreadPool返回的是一个代理对象,屏蔽了ThreadPoolExecutor的一些set方法,即newSingleThreadPool一旦返回,就无法在重新配置线程池的参数了。
3.CachedThreadPool的corePoolSize=0,即核心线程池默认为空,maximumPoolSize=Integer.MAX_VALUE,最大线程池为无限大的。且空闲线程等待新任务超过60秒即被终止。
5. Executor生命周期
由于Executor以异步方式来执行任务,因此在任意时刻,之前提交的任务的状态是无法立刻得到的。有些任务可能已经完成,有些可能正在运行,而其他的任务可能在队列中等待执行。
为了解决执行任务的生命周期问题,ExecutorService扩展了Executor接口,添加了一些生命周期管理的方法。如下:
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
// ...还有用于提交任务的一些方法
线程池的生命周期有以下几种状态
RUNNING:接受新task,并且处理工作队列中的任务。
SHUTDOWN:不接受新task,但是继续处理工作队列中的任务。
STOP:不接受新task,不处理工作队列中的任务,并且中断运行中的线程。
TIDYING:所有任务已被终止,线程池已清空(workerCount=0),此时线程池状态变为TIDYING,并且准备执行terminated()方法。
TERMINATED:以完成terminated()方法。
线程池如何存储自身状态的?
线程池的状态信息是用一个AtomicInteger类型的变量ctl存储的,定义如下:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
ctl中除了存放状态信息,还存放了线程池当前工作线程的个数信息。下图展示这两个信息在ctl中的存储形式:
下面是状态相关信息的源码,结合上图应该就不难理解了
private static final int COUNT_BITS = Integer.SIZE - 3;//为啥减3
//0-28位全为1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// RUNNING : 111。。。
private static final int RUNNING = -1 << COUNT_BITS;
// SHUTDOWN : 000。。。
private static final int SHUTDOWN = 0 << COUNT_BITS;
// STOP : 001。。。
private static final int STOP = 1 << COUNT_BITS;
// TIDYING : 010。。。
private static final int TIDYING = 2 << COUNT_BITS;
//TERMINATED :110。。。
private static final int TERMINATED = 3 << COUNT_BITS;
// 通过简单的位运算获取ctl中的信息
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; }
runState有5种状态,所以最少需要3bit来表示。这就是为什么COUNT_BITS=32-3
各种状态之间的转换时机:
RUNNING -> SHUTDOWN:调用shutdown()方法
(RUNNING or SHUTDOWN) -> STOP:调用shutdownNow()
SHUTDOWN -> TIDYING:线程池和工作队列都为空时
STOP -> TIDYING:线程池为空时
TIDYING -> TERMINATED:调用terminated()后
关闭线程池方法
shutdown():不再接受新任务,同时已提交的任务执行完成,包括那些还在队列中等待,未开始执行的任务。
shutdownNow():将取消所有运行中的任务,并且不再启动队列中尚未开始执行的任务。之后再提交任务则抛出异常:java.util.concurrent.RejectedExecutionException。
6. 线程池工作流程
线程池处理新提交任务的流程如下:
如果当前运行的线程数小于配置的corePoolSize,就新建一个线程执行该command任务,即时此时线程池中有空闲线程。
如果线程池中线程个数达到corePoolSize,新提交的任务就被放入workQueue,等待线程池任务调度。
当workQueue满后,且maximumPoolSize > corePoolSize,新提交任务会创建新线程执行任务
当workQueue满后,且线程池个数达到maximumPoolSize,则提交的任务交由RejectedExecutionHandler处理。
超过corePoolSize的线程,在空闲keepAliveTime时间后,将被关闭。
当allowCoreThreadTimeOut=true时,corePoolSize个数内的线程空闲时间达到keepAliveTime后,也将被关闭。
ThreadPoolExecutor 中execute源码:如下:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
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);
}
7. 线程池监控
//线程池已完成任务数量
private long completedTaskCount;
//当前运行的线程数
public int getActiveCount()
此外,ThreadPoolExecutor还提供了以下几个钩子函数用于扩展它的行为,我们可以在子类中实现自己的逻辑,在每个任务执行的前、后以及worker退出时进行定制处理。
protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Runnable r, Throwable t) { }
protected void terminated() { }
8. 线程的生命周期
下面从线程池中的某个线程的角度出发,分析一下线程从被创建一直到被销毁,整个生命周期里的工作流程
8.1 线程的创建时机
提交任务时被创建(即客户端调用submit方法)。不过提交任务未必一定会创建线程,这在前面线程池的工作流程里已经提到。
预先启动线程池中核心线程池。(如:调用prestartCoreThread、prestartAllCoreThreads()等方法),下面是prestartAllCoreThreads的源码
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true))//firstTask为null
++n;
return n;
}
8.2. 线程在线程池中的数据结构
线程池中的工作线程是被封装到一个Worker类中,部分源码如下:
private final class Worker extends AbstractQueuedSynchronizer
implements Runnable
{
/** worker关联的线程 */
final Thread thread;
/** worker的第一个任务,可能为null */
Runnable firstTask;
/** worker完成的任务数量 */
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** 将任务代理给runWorker方法 */
public void run() {
runWorker(this);
}
。。。。
}
//下面是ThreadPoolExecutor中的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 {
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);
}
}
当firstTassk为null的情况下,线程的执行流程如下图
对于Executor框架,需要明白以下两点
Executor框架基于生产者-消费者模式:提交任务的执行者是生成者,执行任务的线程是消费者。
Executor是异步执行任务,这是通过队列来实现的。