转自 https://www.jianshu.com/p/5b692c96e08d
很详细!
结合项目代码 和 Java 四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor
视频中的部分截图:
在前面使用的例子用,我们已经使用过线程池,基本上就是初始化线程池实例
之后,把任务丢进去
,等待调度执行
就可以了,使用起来非常简单、方便
。虽然使用很简单,但线程池涉及到的知识点非常多。需要分析其实现。
JAVA中Thread
这个类是线程类,在JAVA基础时,对于线程的认识是基于此类,为什么不使用Thread直接执行线程例子呢,而要使用线程池?
可以试想,当并发数量很多
,并且每个线程都是执行一个时间很短的任务
就结束了,这样频繁创建线程
就会大大降低系统的效率
,因为频繁创建线程和销毁线程需要时间
。而线程池可以达到这样的效果
:线程可以复用
,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务。
新建对象
,性能差;缺乏统一管理
,可能无限制的新建线程
,相互竞争
,有可能占用过多系统资源导致死机或OOM
;缺少更多的功能
,如更多执行、定期执行、线程中断;重用存在的线程
,减少对象创建、消亡的开销
,性能佳,降低资源消耗;有效控制最大并发线程数
,提高系统资源利用率
,同时可以避免过多资源竞争
,避免阻塞
,提高响应速度
;防止因为消耗过多的内存
,而把服务器累趴下
(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)定时执行
、定期执行
、单线程
、并发数控制
等功能,以达到提高线程的可管理性。阿里发布的 Java 开发手册中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Executors利用工厂模式向我们提供了4种线程池实现方式,但是并不推荐使用,原因是使用Executors创建线程池不会传入相关参数而使用默认值所以我们常常忽略了那些重要的参数(线程池大小、缓冲队列的类型等),而且默认使用的参数会导致资源浪费,不可取。
Constructor And Parameters
java.uitl.concurrent.ThreadPoolExecutor 类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类,因此我们直接上源码:
public class ThreadPoolExecutor extends AbstractExecutorService {
/** 构造函数 1 */
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue) {}
/** 构造函数 2 */
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory) {}
/** 构造函数 3 */
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
RejectedExecutionHandler handler) {}
/** 构造函数 4 */
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
}
ThreadPoolExecutor 类继承结构是: Executor(I) <- ExecutorService(I) <- AbstractExecutorService© <- TreadPoolExecutor
ThreadPoolExecutor类中提供了四个构造方法,在构造函数4中,参数最多,通过观察其他3个构造函数,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。
构造器中各个参数的含义:
corePoolSize: 核心池的大小
,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务
,除非调用了预创建线程的方法
,即在没有任务到来之前就创建 corePoolSize 个线程或者 一个线程:
prestartCoreThread() : 预创建一个核心线程
,使其闲置等待工作
。
prestartAllCoreThreads() : 启动所有核心线程
,导致它们空闲地等待工作
。
默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后
,就会把到达的任务放到 缓存队列
当中;
maximumPoolSize:线程池最大线程数
,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程
;
keepAliveTime:表示线程没有任务执行时,最多保持多久时间会终止
。默认情况下,只有
当线程池中的线程数大于corePoolSize时
,keepAliveTime才会起作用
,直到线程池中的线程数不大于corePoolSize
,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法
,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
unit:参数keepAliveTime的时间单位
,有7种取值,在TimeUnit类中有7种静态属性
:
TimeUnit.DAYS : 以 天 为单位 ;
TimeUnit.HOURS : 以 小时 为单位 ;
TimeUnit.MINUTES : 以 分钟 为单位 ;
TimeUnit.SECONDS : 以 秒 为单位 ;
TimeUnit.MILLISECONDS : 以 毫秒 为单位 ;
TimeUnit.MICROSECONDS : 以 微秒 为单位 ;
TimeUnit.NANOSECONDS : 以 纳秒 为单位 ;
workQueue: 一个阻塞队列
,用来存储等待执行的任务
,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue:基于数组
的先进先出队列
,此队列创建时必须指定大小
;
LinkedBlockingQueue:基于链表
的先进先出队列
,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE
;
SynchronousQueue :这个队列比较特殊,它不会保存提交的任务
,而是将直接新建一个线程来执行新来的任务
。
一般使用LinkedBlockingQueue和SynchronousQueue
threadFactory:线程工厂
,主要用来创建线程
。 线程池最主要的一项工作,就是在满足某些条件的情况下创建线程。而在ThreadPoolExecutor线程池中,创建线程的工作交给ThreadFactory来完成。要使用线程池,就必须要指定ThreadFactory。 如果我们使用的构造函数时并没有指定使用的ThreadFactory,这个时候ThreadPoolExecutor会使用一个默认的ThreadFactory:DefaultThreadFactory(这个类在Executors工具类中);
handler:在ThreadPoolExecutor线程池中还有一个重要的接口:RejectedExecutionHandler。当提交给线程池的某一个新任务无法直接被线程池中“核心线程”直接处理
,又无法加入等待队列
,也无法创建新的线程执行
;又或者线程池已经调用shutdown()方法停止了工作
;又或者线程池不是处于正常的工作状态
;这时候ThreadPoolExecutor线程池会拒绝处理这个任务
,触发创建ThreadPoolExecutor线程池时定义的RejectedExecutionHandler接口的实现,表示当拒绝处理任务时的策略,有以下四种取值,四种值都为其静态内部类:
CallerRunsPolicy:主线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制
,能够减缓新任务的提交速度
。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); }}
这个策略显然不想放弃执行任务
。但是由于池中已经没有任何资源
了,那么就直接使用调用该execute的线程本身来执行。(开始我总不想丢弃任务的执行,但是对某些应用场景来讲,很有可能造成当前线程也被阻塞。如果所有线程都是不能执行的,很可能导致程序没法继续跑了。需要视业务情景而定吧。)
AbortPolicy:处理程序遭到拒绝将抛出运行时 RejectedExecutionException
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {throw new RejectedExecutionException();}
这种策略直接抛出异常,丢弃任务
。(jdk默认策略,队列满并线程满时直接拒绝添加新任务,并抛出异常,所以说有时候放弃也是一种勇气,为了保证后续任务的正常进行
,丢弃一些也是可以接收的
,记得做好记录
)
DiscardPolicy:不能执行的任务将被删除
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}
这种策略和AbortPolicy几乎一样,也是丢弃任务,只不过他不抛出异常
。
DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除
,然后重试执行程序
(如果再次失败,则重复此过程
)
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) {e.getQueue().poll();e.execute®; }}
该策略就稍微复杂一些,在pool没有关闭的前提下
,首先丢掉缓存在队列中的最早的任务
,然后重新尝试运行该任务
。这个策略需要适当小心。
介绍完上面的构造函数以及其参数之后,介绍一下ThreadPoolExecutor的运行原理,在网上浏览到一篇相关文章,因此将它直接引用过来
深入理解java线程池—ThreadPoolExecutor,以下内容引用此文章,并且增加了自己的一点理解
ThreadPoolExecutor.execute()
向线程池中提交一个不需要返回结果的任务
public void execute(Runnable command) {
//任务为null,则抛出异常
if (command == null)
throw new NullPointerException();
//取出记录着runState和workerCount 的 ctl的当前值
int c = ctl.get();
/**
* 1.第一步:
* 通过workerCountOf方法从ctl所表示的int值中提取出低29位的值,也就是当前活动的线程数。
* 如果当前活动的线程数少于corePoolSize,则通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中
*/
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
/**
* 2.第二步:
* 2.1 isRunning(c) 当前线程池是否处于运行状态。源代码是通过判断c < SHUTDOWN 来确定返回值。由于RUNNING才会接收新任务,且只有这个值-1才小于SHUTDOWN
* 2.2 workQueue.offer(command) 任务添加到缓冲队列
*/
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
/**
* 如果 线程池已经处于非运行状态,则从缓冲队列中移除任务然后采用线程池指定的策略拒绝任务
*/
if (! isRunning(recheck) && remove(command))
reject(command);
/**
* 如果 线程池中任务数量为0,则通过addWorker(null, false)尝试新建一个线程,新建线程对应的任务为null
*/
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/**
* 3.第三步,也就是以上两个步骤都不满足:
* 3.1 当前线程池并不处于Running状态
* 3.2 当前线程池处于Running状态,但是缓冲队列已经满了
*/
else if (!addWorker(command, false))
reject(command);
}
从上面execute()方法中,出现了ctl,跟踪源码分析起作用:
//将整型的32位分为高3位和低29位,高3位表示线程池的状态,低29位表示活动的线程数
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//获得高三位
private static final int COUNT_BITS = Integer.SIZE - 3;
//29位能表示的最大二进制整数,也就是活动线程数
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//运行状态是存储在高三位中
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;
线程池是通过Integer类型的高3位
表述当前线程池的状态RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED
。低29位
表示当前线程的运行任务数量
。然后通过位运算来计算运行状态
和任务的数量
。
execute方法处理流程
从上面的流程图可以知道,向线程池提交一个任务
后,共经历以下流程:
线程池在执行execute(Runnable)
,即提交任务
如果当前运行的线程少于corePoolSize
,则创建新线程
来执行任务(需要获得全局锁
)。如果运行的线程等于或多于corePoolSize
,则将任务加入BlockingQueue
如果无法将任务加入BlockingQueue(队列已满
),则创建新的线程来处理任务
(需要获得全局锁)
如果创建新线程将使当前运行的线程超出maxiumPoolSize
,任务将被拒绝
,并调用RejectedExecutionHandler.rejectedExecution()方法
线程池采取上述的流程进行设计是为了减少获取全局锁的次数。在线程池完成预热(当前运行的线程数大于或等于corePoolSize
)之后,几乎所有的execute方法调用都执行步骤2。
ThreadPoolExecutor.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.
//条件代表着以下几个场景,直接返回false说明当前工作线程创建失败
//1.rs>SHUTDOWN 此时不再接收新任务,且所有的任务已经执行完毕
//2.rs=SHUTDOWN 此时不再接收新任务,但是会执行队列中的任务
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
//先判断当前活动的线程数是否大于最大值,如果超过了就直接返回false说明线程创建失败
//如果没有超过再根据core的值再进行以下判断
//1. core为true,则判断当前活动的线程数是否大于corePoolSize
//2. core为false,则判断当前活动线程数是否大于maximumPoolSize
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//比较当前值是否和c相同,如果相同,则改为c+1,并且跳出大循环,直接执行Worker进行线程创建
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
//检查下当前线程池的状态是否已经发生改变
//如果已经改变了,则进行外层retry大循环,否则只进行内层的循环
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 {
//Worker的也是Runnable的实现类
w = new Worker(firstTask);
//因为不可以直接在Worker的构造方法中进行线程创建
//所以要把它的引用赋给t方便后面进行线程创建
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);//将创建的线程添加到workers容器中
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;
}
Worker
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable{
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
}
Worker
在ThreadPoolExecutor为一个内部类实现了Runnable接口
。只有一个构造方法,在上面的addWorker()中final Thread t = w.thread;知道其实是获取了线程的对象,因为在构造方法中,线程的引用即是它自己。
因此在调用t.start()执行的是(Worker类中的方法):
/** Delegates main run loop to outer runWorker */
public void run() {
//这里执行的是ThreadPoolExecutor中的runWorker
runWorker(this);
}
ThreadPoolExecutor.runWorker()
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;//获取Worker中的任务
w.firstTask = null; //将Woeker中的任务置空
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//如果当前任务为空 那么就从getTask中获得任务
/**
* 如果task不为空,执行完task后则将task置空
* 继续进入循环,则从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 {
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()
。
beforeExecute(Thread t, Runnable r)与afterExecute(Runnable r, Throwable t)并未在类中有处理业务的逻辑
,即可以通过继承线程池的方式来重写这两个方法,这样就能够对任务的执行进行监控
。
这里我有两个疑问?
★ 怎么退出这个While循环,也就是进入到processWorkerExit()
从While循环体中可以知道,当线程运行时出现异常,那么都会退出循环,进入到processWorkerExit()
从getTask()获得结果为null,则也会进到processWorkerExit()
★ 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?
//如果设置了allowCoreThreadTimeOut(true)
//或者当前运行的任务数大于设置的核心线程数
// timed = true
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
/** ------------------------以上的操作跟之前类似----------------------- */
/** ------------------------关键在于下面的代码------------------------- */
/** ------------------------从阻塞队列中获取任务----------------------- */
try {
Runnable r = timed ?
//对于阻塞队列,poll(long timeout, TimeUnit unit) 将会在规定的时间内去任务
//如果没取到就返回null
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
//take会一直阻塞,等待任务的添加
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
到此,终于发现为什么线程池能够保证一直等待任务而不被销毁
,其实就是 进入了阻塞状态
。
ThreadPoolExecutor.processWorkerExit()
/**
* @param completedAbruptly 工作线程是否死与执行任务出现的异常
*/
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) //如果突然被打断,工作线程数不会被减少
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
//判断运行状态是否在STOP之前
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {//正常退出,也就是task == null
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
//新增一个工作线程,代替原来的工作线程
addWorker(null, false);
}
}
AbstractExecutorService.submit()
向线程池中提交一个需要返回结果的任务
public Future> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public Future submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public Future submit(Callable task) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
在ThreadPoolExecutor中并未发现submit(),因此从父类
,即抽象类
AbstractExecutorService中找到submit()的方法实现
,从方法实现中,可知:
submit()接收任务参数,并将参数封装为FutureTask任务类
将封装好的FutureTask提交到execute()
中
结论:submit()真正实现的任务处理流程跟execute()一样,也可以说submit()就是调用了execute()
ThreadPoolExecutor.shutdown()
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();//检查终止线程池的线程是否有权限。
advanceRunState(SHUTDOWN);// 设置线程池的状态为关闭状态。
interruptIdleWorkers();// 中断线程池中空闲的线程
onShutdown(); // 钩子函数,在ThreadPoolExecutor中没有任何动作
} finally {
mainLock.unlock();
}
tryTerminate();// 尝试终止线程池
}
更多该章节的知识,请看
https://www.cnblogs.com/zhujiabin/p/5404771.html
newSingleThreadExecutor:创建一个单线程的线程池
。这个线程池只有一个线程在工作
,也就是相当于单线程串行执行所有任务
。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它
。此线程池保证所有任务的执行顺序按照任务的提交顺序执行
。
newFixedThreadPool:创建固定大小的线程池
。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小
。线程池的大小一旦达到最大值就会保持不变
,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
。(我用的就是这个,同上所述,相当于
创建了相同corePoolSize、maximumPoolSize的线程池
)
newCachedThreadPool:创建一个可缓存的线程池
。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程
,当任务数增加时,此线程池又可以智能的添加新线程来处理任务
。此线程池不会对线程池大小做限制
,线程池大小完全依赖于操作系统
(或者说JVM)能够创建的最大线程大小。
newScheduledThreadPool:创建一个定长线程池
,支持定时及周期性任务执行
。