- 参考博客:
点击!!!!!点击!!!!!点击!!!!!点击!!!!!点击!!!!!点击!!!!!延迟队列详情点击!!主要参考,点击!!!!!
- 为什么提出线程池?
-
- 什么是线程池技术?
- 什么时候用线程池计数?
- 线程池的优势:
- 线程池的设计思路及执行流程:
- 线程池的结构
-
- 线程池之任务的实现方式:
- 浅谈线程池ThreadPoolThread:
-
- ThreadPoolThread的属性:
- ThreadPoolThread的构造方法:
-
- 七个小矮人之第五个小矮人:任务队列
- 七个小矮人之第六个小矮人:线程工厂
- 七个小矮人之第七个小矮人:拒绝策略
- ThreadPoolThread方法:
-
- ThreadPoolThread方法之线程初始化:
- ThreadPoolThread`方法之线程池关闭:
- ThreadPoolThread`方法之线程容量调整:
- ThreadPoolThread`方法之线程池监控:
- 线程池ThreadPoolThread使用:
- Executors封装的四种常见的线程池
- 线程池源码级分析
-
- ThreadPoolThread源码解析之execute():
- ThreadPoolThread源码解析之addWorker():
- ThreadPoolThread内部类Worker源码解析:
- ThreadPoolThread源码解析之runWorker():
- ThreadPoolThread源码解析之getTask():
- ThreadPoolThread源码解析之processWorkerExit():
- AbstractExecutorService源码解析之Callable任务带来的异常不输出问题:
- 到底就结束了对线程池的相关学习,感谢各位大佬的博客借鉴。
为什么提出线程池?
- 其实很多设计的产生,都源自生活需要,有一句话说的好:科技发展不是因为懒,而是为了节约更多的时间高效率的做更多工作。线程池的产生,也是如此。
- 在学习的过程中,线程的重要性不言而喻。有时我们需要多次使用线程,既然用到线程,我们就要创建并且销毁线程,而这个创建和销毁的过程都需要映射到操作系统,并且会消耗一定的内存,而内存资源是非常宝贵的,因此其代价是比较高昂的,所以我们就想是否可以不要每次都创建和销毁线程,这时,线程池应运而生。
- 我们举一个开发过程中的例子:
- 假设一个服务器一天要处理20000个请求,并且每个请求需要一个单独的线程完成,而每个线程执行的时间非常短,这样就会频繁的创建和销毁线程,这样就大大降低了系统的效率,并且可能出现服务器在为每个请求创建新线程和销毁线程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多。
- 那么有没有一种办法使执行完一个任务,并不被销毁,而是可以继续执行其他的任务呢?
- 这就是线程池的目的了。线程池为线程生命周期的开销和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。
什么是线程池技术?
- “线程池”,顾名思义就是一个线程缓存,线程是稀缺资源,如果被无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,因此Java中提供线程池对线程进行统一分配、调优和监控;
什么时候用线程池计数?
线程池的优势:
- 降低资源消耗: 线程池通常会维护一些线程(数量为
corePoolSize
),这些线程被重复使用来执行不同的任务,任务完成后不会销毁。在待处理任务量很大的时候,通过对线程资源的复用,避免了线程的频繁创建与销毁,从而降低了系统资源消耗。
- 提高响应速度: 由于线程池维护了一批
alive
状态的线程,当任务到达时,不需要再创建线程,而是直接由这些线程去执行任务,从而减少了任务的等待时间。
- 提高线程的可管理性: 使用线程池可以对线程进行统一的分配,调优和监控。
线程池的设计思路及执行流程:
- 我们通过一个工厂的设计流程来对比线程池(图和思路来自另一篇博客,文章头有引用地址):
- 通过图我们可以这样进行对比:
- 工厂流程: 工厂中有固定的一批工人,我们称之为正式员工,一般不会对他们进行裁员,并且大多数订单也是由他们完成,但是如果赶上"双11",那么订单量就会暴涨,我们虽然可以先保存一些订单,但是如果订单量过大,那么可能时间上来不及,这时候我们就要雇佣一些临时工来完成暴增的订单,如果这样还不够,那么我们要认同割爱,放其一些订单了。
- 总而言之分为4步:
- 订单1:这一部分由正式工完成; -------------》正式工对应核心线程
- 订单2:这一部分新增的订单存放在仓库; -------------》临时工对应普通线程
- 订单3:如果仓库都放满了,这时候要雇佣临时来完成订单; -------------》仓库对应任务队列
- 订单4:订单太多,不要了;-------------》不要了对应拒绝策列
- 线程池流程:
- 任务进来时,首先执行判断,判断当前线程数量(
worker
)是否小于核心线程(corePoolSize
),如果是,创建核心线程并执行任务;
- 如果不是,则判断任务队列(
workQueue
)中是否有地方存放该任务,如果有,就将任务保存在任务队列中,等待执行;
- 如果任务队列满了,在判断当前线程数是否小于最大线程数(
maximumPoolSize
),如果没有超出这个数量,就创建普通线程(worker
)执行任务;
- 如果超出了,就调用handler实现拒绝策略(
RejectedExecutionHandler
);
- 我们对上面的4步做一个流程图来进一步理解线程池:
- 大体是以上四步,但是还有一些细节需要我们知道,比如调度员对应getTask(),订单对应任务(Runnable),工厂对应线程池,拒绝策略有哪几种,任务队列有哪几种,后面都会有详细的讲解;
线程池的结构
- 我们先看一下Java线程池框架体系:
- 通过这两张图我们可以发现,
Executor
下有一个重要子接口ExecutorService
,其中定义了线程池的具体行为:在这里我们先提几个:
- execute(Runnable command): 执行
Ruannable
接口实现的任务;
- submit(task): 可用来提交
Callable
或Runnable
任务,并返回代表此任务的Future
对象,这是平时学习不太用到的;
- shutdown(): 使线程池进入
shutdown
状态,不再接收新任务,但是可以处理任务队列中的任务;
- shutdownNow(): 使线程池进入
shutdownNow
状态,什么任务也不执行了;
线程池之任务的实现方式:
- 我们把任务通过两个接口实现,以有无返回值为区分点,如下:
Runnable,Thread,Callable
public interface Runnable {
public abstract void run();
}
public interface Callable<V> {
V call() throws Exception;
}
浅谈线程池ThreadPoolThread:
- 我们首先看一下
ThreadPoolThread
的属性以及构造方法,因为我们可以通过这个ThreadPoolThread
来自主设计线程池;
ThreadPoolThread的属性:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
- ctl: 是对线程池的运行状态和线程数量进行控制的一个字段,具体怎么表示看上述源码中的注释就好了;
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;
- 我们通过一张图来进一步描述上面5种状态:
private final BlockingQueue<Runnable> workQueue;
private final ReentrantLock mainLock = new ReentrantLock();
private final HashSet<Worker> workers = new HashSet<>();
private int largestPoolSize;
private long completedTaskCount;
private volatile ThreadFactory threadFactory;
private volatile RejectedExecutionHandler handler;
private volatile long keepAliveTime;
private volatile boolean allowCoreThreadTimeOut;
private volatile int corePoolSize;
private volatile int maximumPoolSize;
ThreadPoolThread的构造方法:
ThreadPoolThread
提供了四个构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
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,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- 我们首先看一下构造线程池的七个"小矮人":
- corePoolSize(必需): 核心线程数,即池中一直保持存活的线程数,即使这些线程处于空闲。但是将
allowCoreThreadTimeOut
参数设置为true后,核心线程处于空闲一段时间以上,也会被回收;
- maximumPoolSize(必需): 池中允许的最大线程数,当核心线程全部繁忙且任务队列存满之后,线程池会临时追加线程,直到总线程数达到
maximumPoolSize
这个上限;
- keepAliveTime(必需): 线程空闲超时时间,当普通线程处于空闲状态的时间超过这个时间后,该线程将被回收。将
allowCoreThreadTimeOut
参数设置为true后,核心线程也会被回收;
- unit(必需):
keepAliveTime
参数的时间单位。
TimeUnit.DAYS
(天)、TimeUnit.HOURS
(小时)、TimeUnit.MINUTES
(分钟)
TimeUnit.SECONDS
(秒)、TimeUnit.MILLISECONDS
(毫秒)、TimeUnit.MICROSECONDS
(微秒)、TimeUnit.NANOSECONDS
(纳秒)
- workQueue(必需): 任务队列,采用阻塞队列实现。当核心线程全部繁忙时,由
execute
方法提交的Runnable任务存放到任务队列中,等待被核心线程处理(当然还有submit
方法);
- 下面就是2个非必须的“小矮人”:
- ThreadFactory(可选) :线程工厂。指定线程池创建线程的方式;
- handler(可选): 拒绝策略,当线程池中线程数达到
maximumPoolSize
且workQueue
装满,后续提交的任务将被拒绝,handler可以指定用什么方式拒绝任务;
- 注意:可以根据应用场景实现
RejectedExecutionHandler
接口,自定义饱和策略;
七个小矮人之第五个小矮人:任务队列
- 使用
ThreadPoolExecutor
需要指定一个实现了BlockingQueue
接口的任务队列;
- 在
ThreadPoolExecutor
线程池的API文档中,一共推荐了三种等待队列,它们分别是:
- SynchronousQueue: 同步队列,该队列没有容量,每一个任务的插入都需要等待一个任务删除操作;
- 注意:如果使用了
SynchronousQueue
队列,通常都需要一个很大的maximumPoolSize
,否则就很容易进入拒绝策略。
- LinkedBlockingQueue:无界任务队列,该阻塞队列的容量可以无限大(
Integer.MAX_VALUE
),直到内存耗尽 (OOM) 为止;
- 基于链表结构,因为任务队列相当于无限大,所以就不需要在线程池中添加普通线程,这样就提高了线程池的吞吐量,但代价就是牺牲了内存空间,甚至OOM;
- 不过使用这个任务队列可以指定容量,这样就相当于有界队列了;
- ArrayBlockingQueue:有界任务队列;
- 基于数组结构,在线程池初始化时,指定队列的容量,后续无法再调整。这种有界队列有利于防止资源耗尽,但可能更难调整和控制;
- Java还提供了另外4种任务队列:
- PriorityBlockingQueue:优先任务队列,放在
PriorityBlockingQueue
中的元素必须实现Comparable
接口,这样才能通过实现compareTo()
方法进行排序:
- 优先级最高的元素将始终排在队列的头部;
PriorityBlockingQueue
不会保证优先级一样的元素的排序,也不保证当前队列中除了优先级最高的元素以外的元素,随时处于正确排序的位置;
- 对于任务执行的先后顺序
ArrayBlockingQueue
和LinkedBlockingQueue
都是先进先出的原则;
- 而对于
PriorityBlockingQueue
则是可以自定义优先级,从而更加灵活的控制线程的执行顺序;
- DelayQueue:延迟队列,基于二叉堆实现,同时具备:无界队列、阻塞队列、优先队列的特征。
DelayQueue
延迟队列中存放的对象,必须是实现Delayed
接口的类对象。通过执行时延从队列中提取任务,时间没到任务取不出来
- LinkedBlockingDeque:双端队列;
- 基于链表结构,既可以从尾部插入/取出任务,还可以从头部插入/任务;
- 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出);
- LinkedTransferQueue: 无界阻塞队列,
- 基于链表结构,这个队列采用一种预占模式;
- 预占模式(生产者消费者模式):
- 消费者线程取元素时,如果队列不为空,则直接取走数据;
- 若队列为空,那就生成一个空节点入队,然后消费者线程被等待在这个节点上;
- 后面生产者线程入队时发现有一个元素为null的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素;
七个小矮人之第六个小矮人:线程工厂
- 对于线程工厂(两点):
- 上面的参数介绍已经说过,线程工厂是为了指定线程创建的方式,Executors类已经为我们非常贴心地提供了一个默认的线程工厂;
- 但是有时候默认的线程工厂并不是我们所需要的,比如在使用线程池的过程中,我们需要在日志中打出可以方便让人理解的线程信息,那么重写ThreadFactory是一个不二的选择;
package java.util.concurrent;
public interface ThreadFactory {
Thread newThread(Runnable r);
}
private 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-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement() , 0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
package com.wwj.threadpoolexecutorDemos;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadFactoryDemo 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;
private String moduleName;
private String functionName;
public ThreadFactoryDemo(String moduleName , String functionName){
this.moduleName = moduleName == null ? "" : moduleName;
this.functionName = functionName == null ? "" : functionName;
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = this.moduleName + "-" + this.functionName + "-pool-" + poolNumber.getAndIncrement() + "-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
七个小矮人之第七个小矮人:拒绝策略
- 拒绝策略是当系统超负荷运转的时候,有可能出现的情况;具体是哪一步发生我们再看流程图:
- JDK本身提供了4种内置的拒绝策略:
AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy和DiscardPolicy
,下面分别介绍这几个策略:
- AbortPolicy(默认): 直接抛出异常
RejectedExecutionException
,默认策略;
- DiscardPolicy: 直接丢弃任务,不抛出异常;
- DisCardOldSetPolicy: 丢弃同步队列中靠最前的任务,并执行当前任务;
- CallerRunsPolicy: 交由任务的调用线程来执行当前任务;
- 除了上面的四种拒绝策略,还可以通过
RejectedExecutionHandler
接口,实现自定义的拒绝策略:
- 源码:
package java.util.concurrent;
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
package com.wwj.threadpoolexecutorDemos;
import com.wwj.lockdemos.ReentrantLockReadWriteLockDemo;
import java.util.concurrent.*;
public class RejectedExecutionHandleDemo implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread()+"Thread:ID:"+Thread.currentThread().getId());
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
RejectedExecutionHandleDemo rejectedExecutionHandleDemo = new RejectedExecutionHandleDemo();
ExecutorService exec = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10),
Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString()+"is discard");
}
});
for(int i=0 ; i<Integer.MAX_VALUE;i++){
exec.submit(rejectedExecutionHandleDemo);
Thread.sleep(10);
}
}
}
- 通过结果我们可以发现:每有5个线程执行,就会有相应的拒绝策略,然后有对应的输出。
ThreadPoolThread方法:
ThreadPoolThread方法之线程初始化:
- 默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程;
- 在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
- prestartCoreThread(): boolean prestartCoreThread(),初始化一个核心线程;
- prestartAllCoreThreads(): int prestartAllCoreThreads(),初始化所有核心线程,并返回初始化的线程数;
- 源码:
public boolean prestartCoreThread() {
return workerCountOf(ctl.get()) < corePoolSize && addWorker(null, true);
}
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true))
++n;
return n;
}
ThreadPoolThread`方法之线程池关闭:
ThreadPoolExecutor
提供了两个方法,用于线程池的关闭;
- shutdown(): 不会立即终止线程池,而是要等所有任务队列中的任务都执行完后才终止,但再也不会接受新的任务;
- shutdownNow(): 立即终止线程池,并尝试打断正在执行的任务,并且清空任务队列,返回尚未执行的任务;
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown();
} finally {
mainLock.unlock();
}
tryTerminate();
}
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
ThreadPoolThread`方法之线程容量调整:
ThreadPoolExecutor
提供了动态调整线程池容量大小的方法:
- setCorePoolSize: 设置核心池大小;
- setMaximumPoolSize: 设置线程池最大能创建的线程数目大小;
ThreadPoolThread`方法之线程池监控:
public long getTaskCount()
public long getCompletedTaskCount()
public int getPoolSize()
public int getActiveCount()
线程池ThreadPoolThread使用:
- 对于ThreadPoolExecutor本身,我们也可以对自定义的线性池进一步编译其行为,这需要重写下面几个方法(具体为什么要重写这几个方法后面会分析):
protected void afterExecute(Runnable r, Throwable t) {
}
protected void terminated() {
}
protected void terminated() {
}
package com.wwj.threadpoolexecutorDemos;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ExecutorPoolExecutorDemo1 implements Runnable {
private String name;
public ExecutorPoolExecutorDemo1(String name){
this.name = name;
}
@Override
public void run() {
System.out.println("正在执行:Thread ID :" + Thread.currentThread().getId() + " Nmae = " + Thread.currentThread().getName());
}
public static void main(String[] args) throws InterruptedException{
ExecutorService exec = new ThreadPoolExecutor(2,2,0l, TimeUnit.SECONDS,new LinkedBlockingDeque<>()){
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
System.out.println("准备执行" + ((ExecutorPoolExecutorDemo1)r).name );
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
System.out.println("执行完成:" + ((ExecutorPoolExecutorDemo1)r).name);
}
@Override
protected void terminated() {
super.terminated();
System.out.println("线程池退出");
}
};
for(int i=0 ; i<2 ; i++){
ExecutorPoolExecutorDemo1 executorPoolExecutorDemo1 = new ExecutorPoolExecutorDemo1("wwj-"+1);
exec.execute(executorPoolExecutorDemo1);
Thread.sleep(10);
}
exec.shutdown();
}
}
- 通过结果我们发现,我们输出有更详细的信息,也有助于我们排查错误,JDK中也标注这几个方法可以由用户自行实现,至于为什么,具体逻辑体系 后面讲解;
- 注意: 当我们在运用线性池的时候,一定要注意在任务执行的run()方法的最外层,一定要抓住所有异常,否则线程内部抛出的异常会被吞噬,具体逻辑后面讲解;
Executors封装的四种常见的线程池
- 上面我们已经分析了通过
ThreadPoolExecutor
来自行定义线程池,但是有时候没有必要去自行定义,完全可以用现成的线程池,
- 很多大厂不建议频繁使用这几种线程池,但是我们可以学习其封装细节及思路!
- 那让我们看一看这四种线程池:
- newFixedThreadPool:
- newFixedThreadExecutor(n):固定容量的线程池,无普通线程;
- 每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入任务队列直到前面的任务完成才继续执行;
- 任务队列未指定容量,代表使用默认值
Integer.MAX_VALUE
;
- 适用于:需要控制并发编程的场景
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads , 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),threadFactory);
}
- newSingleThreadExecutor:
- 线程池有且只有一条线程(核心线程), 因此只有一条线程来执行任务;
- 任务队列未指定容量,代表使用默认值
Integer.MAX_VALUE
;
- 适用于:有顺序的任务的应用场景;
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS , new LinkedBlockingQueue<Runnable>()));
- newScheduleThreadExecutor:
- 定时线程池。指定核心线程数量,普通线程数量无限,线程执行完任务立即回收,任务队列为延时阻塞队列;
- 这是一个比较特别的线程池,适用于执行定时或周期性的任务;
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService {
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue());
}
public ScheduledFuture<?> schedule(Runnable command , long delay , TimeUnit unit) {
...
}
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command , long initialDelay , long period , TimeUnit unit)
{
...}
}
package com.wwj.threadpoolexecutorDemos;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduleThreadPoolDemo {
public static void main(String[] args) {
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
Runnable task = new Runnable(){
public void run() {
System.out.println(Thread.currentThread().getName() + "--->运行");
}
};
((ScheduledExecutorService) scheduledThreadPool).schedule(task, 2, TimeUnit.SECONDS);
((ScheduledExecutorService) scheduledThreadPool).scheduleAtFixedRate(task,50,2000,TimeUnit.MILLISECONDS);
}
}
- newCacheThreadExecutor(推荐使用):
- 可缓存线程池,该线程池中没有核心线程,普通线程的数量为
Integer.max_value
,当有需要时创建线程来执行任务,没有需要时则回收线程;
- 适用于:耗时少(这时候线程基本都能保证不是空闲状态,没必要销毁),任务量大的情况;
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
- 线程池大小: 最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 ) CPU数目*
线程池源码级分析
- 在分析前,我们先通过一个图来看一下线程池是如何运行的:
- 我们先看
execute
方法;
ThreadPoolThread源码解析之execute():
- 在
ThreadPoolExecutor
类中,任务提交方法的入口是execute(Runnable command)
方法(submit()
方法本质也是调用了execute()
,只不过 对任务进行了包装,后面会分析);
- 源码详读:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get(); c保存两部分:1、工作线程数(低29位)2、线程池的状态(高3位)
步骤1:当工作线程数<核心线程数,则通过addWorker看能否加入核心线程,失败则步骤2
if (workerCountOf(c) < corePoolSize) {
创建一个核心线程并执行任务,成功则返回,因为不用入队了,否则进入步骤2
if (addWorker(command, true))
return;
c = ctl.get();
}
总而言之,当工作线程>=核心线程数 或者addWorker失败,则进入步骤2
步骤2是关于入任务队列的操作,这是很重要的,因为线程池状态可能在变,需要反复校验,如下:
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);
}
步骤3:如果线程池不是Running状态或任务入列失败,通过addWorker看能否创建普通线程来执行任务,失败则步骤4
else if (!addWorker(command, false))
步骤4:执行失败策略
reject(command);
}
- 通过源码我们可以发现:该方法其实只在尝试做两件事:能否入队➕能否创建线程执行任务;
- 我们简述一遍其流程:
- 第一步: 通过
ctl.get()、workerCountOf
得到包含线程池线程数的值,然后判断是否小于核心线程数,如果小于,则在线程池中创建线程,让其执行任务,否则进入第二步;
- 注意:如果小于,则在线程池中创建核心线程,不过如果创建失败,同样也需要进入步骤二,但是因为创建线程花费了时间,所以要通过
ctl.get()
获得最新的线程池状态和线程池中的线程数。
- 第二步: 当第一步不满足时,就准备进入任务队列,但是要看当前任务队列的状态。所以,我们需要先判断线程池是否处于
Running
状态(只有Running
状态的线程池才可以接受新任务);如果不是Running
状态则进入第四步,如果是Running
状态且任务添加到队列成功则进入第三步,失败也进入第四步;
- 第三步: 至此,说明我们已经判断线程池是
Running
状态,并且入列成功,但是这入列也花费了时间(虽然不多),因此为了保险,我们需要重新判断线程池状态:
- 如果线程池是
Running
状态,或者线程池不是Running
状态但无奈不能在队列中去除刚刚加入的任务(可能已被某线程获取?),那只能去等待执行,与此同时我们需要判断线程池中工作线程是否为0,因为要保证线程池中有空闲线程可以执行任务;
- 如果是0,则调用
addWorker(commond,true)
添加一个线程(这个线程将去获取已经加入任务队列的本次任务并执行,个人认为就是线程池初始化的时候用的)
- 如果不为0,进入步骤4;
- 如果线程不
Running
状态,并且从任务队列中移除此任务成功,则拒绝本次任务;
- 第四步: 将线程池扩容至
maximumPoolSize
并调用 addWorker(commond,false)
方法创建普通线程执行任务,失败则拒绝本次任务。
- 至此
execute
方法结束,我们再看其对应的流程图:
ThreadPoolThread源码解析之addWorker():
addWorker(Runnable firstTask, boolean core)
方法的主要工作是在线程池中创建一个核心 / 普通线程并执行;
- 参数:
- firstTask参数: 新任务;
- core参数:
- 为true: 表示在新增核心线程时会判断当前活动线程数是否少于corePoolSize;
- 为false: 表示新增普通线程前需要判断当前活动线程数是否少于maximumPoolSize;
- 源码详读:
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
return false;
for (;;) {
内循环判断创建线程条件➕增加工作线程
int wc = workerCountOf(c);
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get();
if (runStateOf(c) != rs)
continue retry;
}
}
boolean workerStarted = false; 将创建的工作线程放入到工作线程集合,也就是线程池中并执行
boolean workerAdded = false;
Worker w = null;
try {
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());
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
workers.add(w);
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;
}
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w);
decrementWorkerCount();
tryTerminate();
} finally {
mainLock.unlock();
}
}
- 我们再简述一遍其流程:大致分为三个板块,外层循环、内层循环、核心线程操作;
- 外层循环: 通过if语句判断线程池的状态是否可以新增工作线程,如下:
- 线程池为非Running状态(Running状态则既可以新增核心线程处理新任务也可以处理同步队列的任务);
- 线程为shutdown状态且firstTask为空且队列不为空(可以普通线程来处理队列中的任务,但是不能接收新任务);
- 内层循环: 线程池添加核心线程并返回是否添加成功的结果:
- 首先校验线程池已有线程数量是否超限,超了则返回false,否则进入下一步;
- 通过CAS使工作线程数
compareAndIncrementWorkerCount(c)
+1,成功则进入下一步,失败则再次校验线程池是否是之前的状态,是则继续内层循环,不是则返回外层循环
- CAS操作使核心线程数+1成功后的操作: 将线程添加到工作线程集合,并启动工作线程:
- 判断线程是否为空,非空则获取锁,然后再次校验线程池状态,通过则进入下一步,未通过则添加线程
workerAdded
失败 ;
- 线程池状态校验通过后,再检查线程是否已经启动,是则抛出异常,否则尝试将线程加入线程池;
- 检查线程是否启动成功,成功则返回true,失败则进入
addWorkerFailed
方法;
- 至此
addWorker
方法结束,我们再看其对应的流程图:
ThreadPoolThread内部类Worker源码解析:
Worker
是ThreadPoolExecutor
的一个内部类,我们先看一下Worker的继承关系:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
- 通过其继承关系我们可以知道:
- 它实现了
Runnable
接口,所以可以拿来当线程用;
- 同时它还继承了
AbstractQueuedSynchronizer
同步器类,主要用来实现一个不可重入的锁,之所以不是重入锁,是因为我们不希望工作任务在调用setCorePoolSize
之类的池控制方法时能够重新获取锁;
- 另外,为了在线程真正开始运行任务之前禁止中断,我们将锁状态初始化为负值,并在启动时清除它(在
runWorker
中)。
- 线程池中的每一个线程被封装成一个Worker对象,ThreadPool维护的其实就是一组Worker对象;
- thread是在调用构造方法时通过
ThreadFactory
来创建的线程,是用来处理任务的线程;
Worker
类主要维护正在运行任务的线程的中断控制状态,以及其他相关信息的记录,这个类继承了AbstractQueuedSynchronizer
类,以简化获取和释放锁(该锁作用于每个任务执行代码)的过程。这样可以防止去中断正在运行中的任务,只会中断在等待从任务队列中获取任务的线程。
private final class Worker extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
final Thread thread;
Runnable firstTask;
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
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) {
}
}
}
}
ThreadPoolThread源码解析之runWorker():
runWorker(Worker w)
是线程池中真正处理任务的方法;
- 参数:
- Worker w: 封装的
Worker
,携带了工作线程的诸多要素,包括Runnable(待处理任务)、lock(锁)等
- 我们先看源码再分析;
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
boolean completedAbruptly = true;
try {
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);
try {
task.run();
afterExecute(task, null);
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
- 我们再简述一遍其流程:
- 判断任务队列或者当前任务是否都为空:
- 若都为空,则将
completedAbruptly
置为false
,然后执行processWorkerExit(w,completedAbruptly)
方法进入线程退出程序;
- 若有其一不为空,则进入循环,并加锁;
- 进入循环后,则判断是否需要设置中断标识,以下两个条件都满足则设置中断标识:
- 如果一开始线程池的状态>=stop, 或者 一开始判断线程池状态Thread.interrupted()为
true
,即线程已经被中断,再次检查线程池状态是否>=STOP(以消除该瞬间shutdown方法生效,使线程池处于>=STOP状态);
- wt未设置中断标识;
- 执行前置方法
beforeExecute(wt, task)
(该方法为空方法,由子类实现)后执行task.run()
方法执行任务(执行不成功抛出相应异常);
- 执行后置方法
afterExecute(task, thrown)
(该方法为空方法,由子类实现)后将线程池已完成的任务数+1,并释放锁,请勿清空;
- 再次进行
while
循环;
- 至此
runWorker(Worker w)
方法结束,我们再看其对应的流程图:
ThreadPoolThread源码解析之getTask():
- 在上述
runWorker(Worker w)
中,我们看到了getTask()
方法,通过它的方法名我们也知道它的作用,就是在任务队列中获取任务;
- 源码详读:
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
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 {
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
- 这里重要的地方是第二个
if
判断:目的是控制线程池的有效线程数量;
- 由源码分析可知,在执行execute方法时,如果当前线程池的线程数量超过了
corePoolSize
且小于maximumPoolSize
,并且workQueue
已满时,则可以增加工作线程(普通线程),但这时如果超时没有获取到任务,也就是timedOut
为true
的情况,说明workQueue
已经为空了,也就说明了当前线程池中不需要那么多线程来执行任务了,可以把多于corePoolSize
数量的线程销毁掉,保持线程数量在corePoolSize
即可;
- 那普通线程什么时候会销毁?
- 当然是
runWorker
方法执行完之后,也就是Worker中的run方法执行完,由JVM自动回收。
- 注意:
getTask
方法返回null时,在runWorker
方法中会跳出while循环,然后会执行processWorkerExit
方法。
- 我们再简述一遍其流程:
- 将
timedOut
(获取任务是否超时)置为false(首次执行方法,无上次,自然为false),进入一个无限循环;
- 如果线程池为
Shutdown
状态且任务队列为空(线程池shutdown状态可以处理任务队列中的任务,不再接受新任务,这个是重点)或者线程池状态>=STOP
,则意味着线程池不必再获取任务了,当前工作线程数量-1并返回null,否则接着往下执行;
- 接着if判断是否满足:线程池数量超限制或者时间超限且(任务队列为空或当前线程数>1):
- 满足,则移除工作线程(普通线程),成功则返回null,不成功进入下一轮循环;
- 不满足则继续往下执行;
- 尝试用
poll()
或者 take()
(具体用哪个取决于timed
的值)获取任务:
- 如果任务不为空,则返回该任务;
- 如果为空,则将
timeOut
置为 true进入下一轮循环。如果获取任务过程发生异常,则将 timeOut
置为 false 后进入下一轮循环。
- 至此
getTask()
方法结束,我们再看其对应的流程图:
ThreadPoolThread源码解析之processWorkerExit():
processWorkerExit(Worker w, boolean completedAbruptly)
执行线程退出的方法;
- 参数:
- Worker w: 要结束的工作线程;
- boolean 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();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return;
}
addWorker(null, false);
}
}
- 我们再简述一遍其流程:
- 如果
completedAbruptly
为 true,即工作线程因为异常突然死亡,则执行工作线程-1操作;
- 主线程获取锁后,线程池已经完成的任务数追加 w(当前工作线程) 完成的任务数,并从worker的set集合中移除当前worker;
- 根据线程池状态进行判断是否执行tryTerminate()结束线程池;
- 是否需要增加工作线程,如果线程池还没有完全终止,仍需要保持一定数量的线程;
- 如果当前线程是突然终止的,调用addWorker()创建工作线程
- 当前线程不是突然终止,但当前工作线程数量小于线程池需要维护的线程数量,则创建工作线程。需要维护的线程数量为corePoolSize(取决于成员变量 allowCoreThreadTimeOut是否为 false)或1。
- 我们通过一个图对以上的方法进行一个总结:
AbstractExecutorService源码解析之Callable任务带来的异常不输出问题:
- 在
ThreadPoolExecutor
类中,任务提交方法的入口往往是是execute(Runnable command)
方法,其实submit()
方法本质也是调用了execute()
(当然ThreadPoolExecutor
不能执行这个方法,而是通过ForkJoinPool
来实现的),只不过对任务进行了包装;
- 不过从JDK5以后,工作单元和执行机制已经分离开来:
- 工作单元:
Runnable、Callable
,它们之间的区别前面已经提过;
- 执行机制: 由
Executor
框架提供;
- 我们通过图看一下其结构:
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
- 通过源码可知,对一个有返回值的任务的实现:本质就是对任务做了一层封装,将其封装成
RunnableFuture
对象,而最终它其实是一个 FutureTask
对象,在被添加到线程池的工作队列,然后调用 start()
方法后, FutureTask
对象的 run()
方法开始运行,即本任务开始执行:
public void run() {
if (state != NEW || !RUNNER.compareAndSet(this, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
- 在
FutureTask
对象的 run()
方法中,该任务抛出的异常被捕获,然后放在setException(ex)
方法中;
- 抛出的异常会被放到
outcome
对象中:
protected void setException(Throwable t) {
if (STATE.compareAndSet(this, NEW, COMPLETING)) {
outcome = t;
STATE.setRelease(this, EXCEPTIONAL);
finishCompletion();
}
}
- 这个对象
outcome
就是submit()
方法会返回的FutureTask
对象执行get()
方法得到的结果;
- 但是在线程池中,并没有获取执行该结果,所以异常也就没有被抛出来,即被“吞掉”了,这就是线程池的
submit()
方法提交任务没有异常抛出的原因;
- 不过我们也有对应的解决方法:
- 在定义
ThreadFactory
的时候调用setUncaughtExceptionHandler
方法,自定义异常处理方法。 例如:
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("judge-pool-%d")
.setUncaughtExceptionHandler((thread, throwable)-> logger.error("ThreadPool {} got exception", thread,throwable))
.build();
- 这样,对于线程池中每条线程抛出的异常都会打下 error 日志!!!
到底就结束了对线程池的相关学习,感谢各位大佬的博客借鉴。