简单介绍
开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
线程池的基本认识
什么是线程池?
线程池(Thread Pool)是一种基于「池化思想」管理线程的工具,经常出现在多线程服务器中,如MySQL
。
提前创建好若干个线程放在一个容器中。如果有任务需要处理,则将任务直接分配给线程池中的线程来执行,任务处理完以后这个线程不会被销毁,而是等待后续分配任务
使用线程池有什么好处?
-
降低创建线程和销毁线程的性能开销
通过池化技术复用已创建的线程,降低线程创建和销毁造成的损耗
-
提高相应速度
当有新任务需要执行时不需要等待线程创建就可以立马执行
-
提高线程的可管理性
线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。 合理的的设置线程池大小可以避免因为线程数超过硬件资源瓶颈带来的问题
-
提供更多更强大的功能
线程池具备可扩展性,允许开发人员向其中增加更多的功能,比如延时定时线程池
ScheduledThreadPoolExecutor
,允许任务延时执行或定期执行。
使用线程池为了解决什么问题?
线程池要解决的核心问题就是资源管理问题。
在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入。这种不确定性将带来以下若干问题:
- 频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大。
- 对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险。
- 系统无法合理管理内部的资源分布,会降低系统的稳定性。
也是为了解决上述问题,所以线程池采用了「池化思想」。这个内容将在下面的「线程池原理分析」中解释。
池化思想
什么是池化思想?
池化是将资源统一在一起管理的一种思想,目的是为了最大化收益并最小化风险。
在计算机领域中的表现为:统一管理IT资源,包括服务器、存储、和网络资源等等。通过共享资源。使用户在低投入中获益。
除去线程池,还有其他比较典型的几种使用策略包括:
-
内存池(Memory Pooling):
预先申请内存,提升申请内存速度,减少内存碎片。
-
连接池(Connection Pooling):
预先申请数据库连接,提升申请连接的速度,降低系统的开销。
-
实例池(Object Pooling):
循环使用对象,减少资源在初始化和释放时的昂贵损耗。
接着我们将分析Java中的线程池核心实现类--
ThreadPoolExecutor
ThreadPoolExecutor 分析
Java如何实现和管理线程池的?
从JDK 5开始,JDK把工作单元与执行机制分离开来,工作单元包括Runnable
和Callable
,而执行机制由Executor
框架提供。
Java线程池实现原理其实就是一个线程集合threadSet
和一个阻塞队列blockQueue
。
当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入blockQueue
中。threadSet
中的线程会不断的从blockQueue
中获取线程然后执行。当blockQueue
中没有任务的时候,thread
就会阻塞,直到队列中有任务了就取出来继续执行。
这里我们画图表示一下这个模型:
当然上图只是一个简略的模型,接着我们会在「ThreadPoolExecutor
运行机制」中画出更加详细的模型。
ThreadPoolExecutor 运行机制
ThreadPoolExecutor
的UML
图
-
Executor
接口ThreadPoolExecutor
实现的顶层接口是Executor
,顶层接口Executor
提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供
Runnable
对象,将任务的运行逻辑提交到执行器(Executor
)中,由Executor
框架完成线程的调配和任务的执行部分。 -
ExecutorService
接口ExecutorService
接口增加了一些能力:- 扩充执行任务的能力,补充可以为一个或一批异步任务生成
Future
的方法; - 提供了管控线程池的方法,比如停止线程池的运行。
- 扩充执行任务的能力,补充可以为一个或一批异步任务生成
-
AbstractExecutorService
类AbstractExecutorService
则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。 -
ThradPoolExecutor
类最下层的实现类
ThreadPoolExecutor
实现最复杂的运行部分,ThreadPoolExecutor
将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。
ThreadPoolExecutor
是如何运行,如何同时维护线程和执行任务的呢?
ThreadPoolExecutor
运行机制如下图所示:
线程池在内部实际上构建了一个「生产者消费者模型」,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。
线程池的运行主要分成两部分:任务管理,线程管理
任务管理部分充当生产者的角色,线程管理则作为消费者,当任务提交后,线程池会判断该任务后续的流转:
- 线程池首先判断当前运行的线程数是否少于
corePoolSize
。如果是,则创建一个新的工作线程来执行任务。(当ThreadPoolExecutor
创建新线程时,通过CAS
来更新线程池的状态ctl
)如果都在执行任务,则进入2 - 判断
BlockingQueue
是否已经满了,如果没有满,则将线程放入BlockingQueue
。否则进入3 - 如果创建一个新的工作线程将使当前运行的线程数量超过
maximumPoolSize
,则交给RejectedExecutionHandler
来处理任务。
上述参数我们都可以在ThreadPoolExecutor
的构造函数中看到:
-
ThreadPoolExecutor
构造函数及参数解释:public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) 复制代码
-
corePoolSize
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于
corePoolSize
。即使有其他空闲线程能够执行新来的任务, 也会继续创建线程;如果当前线程数为
corePoolSize
,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的
prestartAllCoreThreads()
方法,线程池会提前创建并启动所有核心线程。 -
maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于
maximumPoolSize
;当阻塞队列是无界队列, 则
maximumPoolSize
则不起作用, 因为无法提交至核心线程池的线程会一直持续地放入workQueue
。 -
keepAliveTime
线程空闲时的存活时间,即当线程没有任务执行时,该线程继续存活的时间;默认情况下,该参数只在线程数大于
corePoolSize
时才有用, 超过这个时间的空闲线程将被终止; -
unit
keepAliveTime
的单位 -
workQueue
用来保存等待被执行的任务的阻塞队列。在JDK中提供了如下阻塞队列:
ArrayBlockingQueue
: 基于数组结构的有界阻塞队列,按FIFO排序任务;LinkedBlockingQueue
: 基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQueue
;SynchronousQueue
: 一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue
;PriorityBlockingQueue
: 具有优先级的无界阻塞队列;
(
LinkedBlockingQueue
比ArrayBlockingQueue
在插入删除节点性能方面更优,但是二者在put()
,take()
任务的时均需要加锁,SynchronousQueue
使用无锁算法,根据节点的状态判断执行,而不需要用到锁,其核心是Transfer.transfer()
。) -
handler
线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
AbortPolicy
: 直接抛出异常,默认策略;CallerRunsPolicy
: 用调用者所在的线程来执行任务;DiscardOldestPolicy
: 丢弃阻塞队列中靠最前的任务,并执行当前任务;DiscardPolicy
: 直接丢弃任务;
当然也可以根据应用场景实现
RejectedExecutionHandler
接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
-
threadFactory
创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。默认为
DefaultThreadFactory
-
Java提供的四种线程池
JDK的线程池实现
以下的方法都是Executors
类中的静态工厂方法,用于创建我们需要的线程池:
-
newFixedThreadPool
-
什么是
newFixedThreadPool
?固定大小的线程池,里面都是核心线程。线程池的线程数量达
corePoolSize
后,即使线程池没有可执行任务时,也不会释放线程。创建它的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } 复制代码
FixedThreadPool
的工作队列为无界队列LinkedBlockingQueue
(队列容量为Integer.MAX_VALUE
), 这会导致以下问题:- 线程池里的线程数量不超过
corePoolSize
,这导致了maximumPoolSize
和keepAliveTime
将会是个无用参数 - 由于使用了无界队列, 所以
FixedThreadPool
永远不会拒绝,即饱和策略失效
- 线程池里的线程数量不超过
-
测试及解释
public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.execute(()->{ log.debug("1"); }); executorService.execute(()->{ log.debug("2"); }); executorService.execute(()->{ log.debug("3"); }); } 复制代码
运行结果如下:
我们注意到执行这三个任务的线程在任务结束以后还是没有停止,这说明线程池的线程需要手动调用结束方法,这点我们后面再说。
我们注意到线程名「pool-1-thread-*」这里对应的源码如下:
通过这个
DefaultThreadFactory
工厂给我们的线程起了名字,使用的是原子整数自增编号。既然看到了这个线程工厂的源码,那么我们也可以给它自定义线程工厂:
@Test public void testDIYFixedThreadPool() throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(2, new ThreadFactory() { private AtomicInteger t = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { return new Thread(r, "mypool_t" + t.getAndIncrement()); } }); executorService.execute(()->{ log.debug("1"); }); executorService.execute(()->{ log.debug("2"); }); executorService.execute(()->{ log.debug("3"); }); Thread.sleep(1000L); } 复制代码
运行结果如下:
-
-
newCachedThreadPool
-
什么是
newCachedThreadPool
?这个是一个带缓冲的线程池。核心线程为0,创建出来的线程都是救急线程,每个线程存活时间为60s。
创建它的方法如下:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } 复制代码
线程池的线程数可达到
Integer.MAX_VALUE
,即2147483647内部使用
SynchronousQueue
作为阻塞队列; 和newFixedThreadPool
创建的线程池不同,newCachedThreadPool
在没有任务执行时,当线程的空闲时间超过keepAliveTime
,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销; -
适用场景
适合任务数比较密集,但每个任务执行时间较短的情况。
-
测试及解释
我们这里主要测试
SynchronousQueue
:@Test public void testCachedThreadPool() throws InterruptedException { SynchronousQueue<Integer> integers = new SynchronousQueue<>(); new Thread(()->{ try{ log.debug("{} 正入队",1); integers.put(1); log.debug("{} 已入队",1); log.debug("{} 正入队",2); integers.put(2); log.debug("{} 已入队",2); }catch (InterruptedException e){ e.printStackTrace(); } },"t1").start(); Thread.sleep(500L); new Thread(()->{ try{ log.debug("取出 {}",1); integers.take(); }catch (InterruptedException e){ e.printStackTrace(); } },"t2").start(); new Thread(()->{ try{ log.debug("取出 {}",2); integers.take(); }catch (InterruptedException e){ e.printStackTrace(); } },"t3").start(); Thread.sleep(5000L); } 复制代码
运行结果:
我们这里发现一个对象放进去后它就阻塞住,等待一个线程来取,否则就不会放下一个。
这个特性就非常适合
newCachedThreadPool
,这个工厂线程池表现出来的特点就是线程数根据任务量不断增长,没有上限,等任务执行完毕,空闲1分钟后释放掉线程。所以说这个线程池适合任务数量大,但是每个任务执行时间比较短。
我们试着使用这个线程池执行100000个任务:
@Test public void testCachedThreadPool() throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 100000; i++) { int count = i; executorService.execute(()->{ log.debug(String.valueOf(count)); }); } executorService.shutdown(); executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES); } 复制代码
运行结果:
这里发现它创建的线程数量至少达到了12824个,也验证了我们上面说的。
-
-
newSingleThreadExecutor
-
什么是
newSingleThreadExecutor
?初始化的线程池中只有一个线程,如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行。
创建该线程池的方法如下:
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } 复制代码
由于适用了无界队列,所以
SingleThreadPool
永远不会拒绝,即饱和策略失效。 -
适用场景
希望多个任务排队执行,线程数固定为1,任务数多于1时,会放入无界队列排队(它保证了阻塞队列重点额任务都是串行的。不会出现并发并行的情况)。任务执行完毕,这唯一的线程也不会被释放。
比如说:分布式情况下,保证双写一致的情况下,需要将相同的业务留给同一台服务器或者线程来处理。
主线程执行正常流程,线程池执行异步流程,比如短信通知。
-
和我们自己创建一个单线程执行的区别?和
newFixedThreadPool(1)
又有什么区别呢?-
自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作
-
Executors.newSingleThreadExecutor()
线程个数始终为1,不能修改FinalizableDelegatedExecutorService
应用的是装饰器模式,只对外暴露了ExecutorService
接口,因此不能调用ThreadPoolExecutor
中特有的方法。 -
Executors.newFixedThreadPool(1)
初始时为1,以后还可以修改对外暴露的是
ThreadPoolExecutor
对象,可以强转后调用setCorePoolSize
等方法进行修改。
-
-
测试及解释
这里我们故意让一个任务有异常:
@Test public void testSingleThreadExecutor() throws InterruptedException { ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.execute(()->{ log.debug("1"); int i=1/0; }); executorService.execute(()->{ log.debug("2"); }); executorService.execute(()->{ log.debug("3"); }); executorService.shutdown(); executorService.awaitTermination(Long.MAX_VALUE,TimeUnit.MINUTES); } 复制代码
运行结果:
-
-
ScheduledThreadPoolExecutor
什么是
ScheduledThreadPoolExecutor
?ScheduledThreadPoolExecutor
继承自ThreadPoolExecutor
,为任务提供延迟或周期执行,属于线程池的一种。它和上面不同的就是它是一个独立的类,而前面的都是工厂方法。
由于需要理解这个类得先理解
FutureTask
类,所以我们把这个类放到后面的博客中介绍。
ThreadPoolExecutor 源码详解
关键属性
//int是32位的,这里把int的高3位拿来充当线程池状态的标志位,后29位拿来充当当前运行worker的数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//存放任务的阻塞队列
private final BlockingQueue<Runnable> workQueue;
//worker的集合,用set来存放
private final HashSet<Worker> workers = new HashSet<Worker>();
//历史达到的worker数最大值
private int largestPoolSize;
//当队列满了并且worker的数量达到maxSize的时候,执行具体的拒绝策略
private volatile RejectedExecutionHandler handler;
//超出coreSize的worker的生存时间
private volatile long keepAliveTime;
//常驻worker的数量
private volatile int corePoolSize;
//最大worker的数量,一般当workQueue满了才会用到这个参数
private volatile int maximumPoolSize;
复制代码
-
workQueue
存放任务的阻塞队列
-
workers
worker
的集合 -
largestPoolSize
历史达到的worker数最大值
-
handler
当队列满了并且
worker
的数量达到maxSize
的时候,执行具体的拒绝策略 -
keepAliveTime
超出
coreSize
的worker
的生存时间 -
corePoolSize
常驻
worker
的数量 -
maximumPoolSize
最大
worker
的数量,一般当workQueue
满了才会用到这个参数。
生命周期
内部状态
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState存储在高阶位中
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;
// 打包和拆箱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; }
复制代码
-
ctl
线程池运行的状态,并不是用户显式设置的,而是伴随着线程池的运行,由内部来维护。线程池内部使用一个变量维护两个值:运行状态(
runState
)和线程数量 (workerCount
)。具体实现就是用了这个ctl
来同时维护这两个值的。ctl
可以利用低29为表示线程池中的线程数,通过高3位表示线程池的运行状态。(
COUNT_BITS
在int是32位的时候是29,于是左移COUNT_BITS
位就是要当前数的前三位)
-
RUNNING: -1 << COUNT_BITS
即高3位为111,该状态的线程池会接收新任务,并处理阻塞队列中的任务;
-
SHUTDOWN: 0 << COUNT_BITS
即高3位为000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;
-
STOP : 1 << COUNT_BITS
即高3位为001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
-
TIDYING : 2 << COUNT_BITS
即高3位为010, 所有的任务都已经终止;
workerCount
(有效线程数)为0 -
TERMINATED: 3 << COUNT_BITS
即高3位为011,
terminated()
方法已经执行完成
获取生命周期状态、获取线程池数量的计算方法
-
runStateOf(int c)
计算当前运行状态
-
workerCountOf(int c)
计算当前线程数量
-
ctlOf(int rs, int wc)
通过状态和线程数生成
ctl
状态(生命周期)之间相互转换如下图所示:
任务执行
线程池中任务的执行链
线程池的工作线程通过Woker
类实现,在ReentrantLock
锁的保证下,把Woker
实例插入到HashSet
后,并启动Woker
中的线程。
Worker
本身是ThreadPoolExecutor
的静态内部类,构造方法如下:
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
复制代码
从Woker
类的构造方法实现可以发现:
线程工厂在创建线程thread
时,将Woker
实例本身this
作为参数传入,当执行start
方法启动线程thread
时,本质是执行了Worker
的runWorker
方法。 对应Worker
中的run
方法如下:
public void run() {
runWorker(this);
}
复制代码
firstTask
执行完成之后,通过getTask
方法从阻塞队列中获取等待的任务,如果队列中没有任务,getTask
方法会被阻塞并挂起,不会占用CPU资源;
接着我们顺着执行链来依次介绍这几个方法。
execute方法
exeucte
方法及源码
ThreadPoolExecutor.execute(task)
实现了Executor.execute(task)
源码如下:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
//workerCountOf获取线程池的当前线程数;小于corePoolSize,执行addWorker创建新线程执行command任务
if (addWorker(command, true))
return;
c = ctl.get();
}
// double check: c, recheck
// 线程池处于RUNNING状态,把提交的任务成功放入阻塞队列中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// recheck and if necessary 回滚到入队操作前,即倘若线程池shutdown状态,就remove(command)
// 如果线程池没有RUNNING,成功从阻塞队列中删除任务,执行reject方法处理任务
if (! isRunning(recheck) && remove(command))
reject(command);
// 线程池处于running状态,但是没有线程,则创建线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 往线程池中创建新的线程失败,则reject任务
else if (!addWorker(command, false))
reject(command);
}
复制代码
所有任务的调度都是由execute
方法完成的,这部分完成的工作是:
检查现在线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。
其执行过程如下:
- 首先检测线程池运行状态,如果不是
RUNNING
,则直接拒绝,线程池要保证在RUNNING
的状态下执行任务。 - 如果
workerCount < corePoolSize
,则创建并启动一个线程来执行新提交的任务。 - 如果
workerCount >= corePoolSize
,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。 - 如果
workerCount >= corePoolSize && workerCount < maximumPoolSize
,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。 - 如果
workerCount >= maximumPoolSize
,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
-
这里为什么需要double check线程池的状态?
在多线程环境下,线程池的状态时刻在变化,而
ctl.get()
是非原子操作,很可能刚获取了线程池状态后线程池状态就噶癌变了。判断是否将command
加入workqueue
是线程池之前的状态。如果没有double check,万一线程池处于非RUNNING
状态(多线程环境下很可能发生),那么command
永远不会执行。
addWorker方法
Worker
线程管理和Worker
线程
线程池为了掌握线程的状态并维护线程的生命周期,设计了**线程池内的工作线程Worker**
。我们来看一下它的部分代码:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
final Thread thread;//Worker持有的线程
Runnable firstTask;//初始化的任务,可以为null
}
复制代码
Worker
这个工作线程,实现了Runnable
接口,并持有一个线程thread
,一个初始化的任务firstTask
:
thread
是在调用构造方法时通过ThreadFactory
来创建的线程,可以用来执行任务;
-
firstTask
用它来保存传入的第一个任务,这个任务可以有也可以为null。如果这个值是非空的,那么线程就会在启动初期立即执行这个任务,也就对应核心线程创建时的情况;
如果这个值是null,那么就需要创建一个线程去执行任务列表(
workQueue
)中的任务,也就是非核心线程的创建。
Worker
执行任务的模型如下图所示:
addWorker
方法有什么用?
addWorker
是Worker
线程管理的增加线程部分。
源码如下:
private final ReentrantLock mainLock = new ReentrantLock();
private boolean addWorker(Runnable firstTask, boolean core) {
// CAS更新线程池数量
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);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
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);
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);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); // 线程启动,执行任务(Worker.thread(firstTask).start());
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
复制代码
addWorker
主要负责创建新的线程并执行任务,线程池创建新线程执行任务时需要获取全局锁。
addWorker
方法有两个参数:firstTask
、core
。
firstTask
参数用于指定新增的线程执行的第一个任务,该参数可以为空;
core
参数为true表示在新增线程时会判断当前活动线程数是否少于corePoolSize
,false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize
,
其执行流程如下图所示:
runWorker方法
Worker
的run方法实际调用的是runWorker
方法
这里我们主要看到Worker
的继承关系和它的构造方法:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this); // 创建线程
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
// ...
}
复制代码
- 继承了
AQS
类,可以方便的实现工作线程的中止操作; - 实现了
Runnable
接口,可以将自身作为一个任务在工作线程中执行; - 当前提交的任务
firstTask
作为参数传入Worker
的构造方法;
一些属性还有构造方法:
//运行的线程,前面addWorker方法中就是直接通过启动这个线程来启动这个worker
final Thread thread;
//当一个worker刚创建的时候,就先尝试执行这个任务
Runnable firstTask;
//记录完成任务的数量
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
//创建一个Thread,将自己设置给他,后面这个thread启动的时候,也就是执行worker的run方法
this.thread = getThreadFactory().newThread(this);
}
复制代码
runWorker
方法
runWorker
方法是线程池的核心:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
// 通过unlock方法释放锁
w.unlock();
boolean completedAbruptly = true;
try {
// 先执行firstTask,再从workerQueue中取task(getTask())
while (task != null || (task = getTask()) != null) {
// 进行加锁操作,保证thread不被其他线程中断(除非线程池被中断)
w.lock();
// 检查线程池状态,倘若线程池处于中断状态,当前线程将中断。
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
// 执行 beforeExecute
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方法
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
// 解锁操作
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
复制代码
线程启动之后,通过unlock
方法释放锁,设置AQS
的state为0,表示运行可中断;Worker
执行firstTask
或从workQueue
中获取任务
getTask方法
getTask
方法
下面来看一下getTask()
方法,这里面涉及到keepAliveTime
的使用,从这个方法我们可以看出线程池是怎么让超过corePoolSize
的那部分worker
销毁的。
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
// 进入自旋状态
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 判断当前的线程池状态是否为SHUTDOWN状态或者STOP状态,或者队列为空,如果满足的话,那么Worker总数减一,并且返回空
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// 当获取到的Task为空的话,那么timeOut标志位被设置成true
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 当WorkreCount小于maximumPoolSize并且没有超时的话,那么就会进入获取Task的阶段,否则则进入销毁Worker的步骤中,如果减少worker失败的话,那么就会重新进入循环,而如果当前状态和之前状态不一致的话,那么就重新回到retry的地方,重新判断状态
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 在获取Task的时候,会判断timed,如果timed为true的话,那么就会从Queue中等到keepAliveTime的时长,如果时间段中有新的任务的话,那么就会返回,否则的话,直接从队列中获取。把获取到的task返回
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
// 如果获取到的runnable为空的话,那么把timedOut设置成true
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
复制代码
- 进入自旋状态
- 判断当前的线程池状态是否为
SHUTDOWN
状态或者STOP
状态,或者队列为空,如果满足的话,那么Worker
总数减一,并且返回空 - 当获取到的
Task
为空的话,那么timeOut
标志位被设置成true
- 当
WorkerCount
小于maximumPoolSize
并且没有超时的话,那么就会进入获取Task
的阶段,否则则进入销毁Worker
的步骤中,如果减少worker
失败的话,那么就会重新进入循环,而如果当前状态和之前状态不一致的话,那么就重新回到retry的地方,重新判断状态
- 而在获取
Task
的时候,会判断timed
,如果timed
为true
的话,那么就会从Queue
中等到keepAliveTime
的时长,如果时间段中有新的任务的话,那么就会返回,否则的话,直接从队列中获取。把获取到的task返回
- 如果获取到的
runnable
为空的话,那么把timedOut
设置成true
注意这里一段代码是keepAliveTime
起作用的关键代码片段:
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
复制代码
allowCoreThreadTimeOut
为false
,线程即使空闲也不会被销毁;倘若为ture
,在keepAliveTime
内仍空闲则会被销毁。
如果线程允许空闲等待而不被销毁timed == false
,workQueue.take
任务(如果阻塞队列为空,当前线程会被挂起等待;当队列中有任务加入时,线程被唤醒,take
方法返回任务,并执行)
如果线程不允许无休止空闲timed == true
, workQueue.poll
任务(如果在keepAliveTime
时间内,阻塞队列还是没有任务,则返回null)
任务提交
任务提交及执行流程
submit
任务,等待线程池execute
- 执行
FutureTask
类的get
方法时,会把主线程封装成WaitNode
节点并保存在waiters
链表中, 并阻塞等待运行结果;
FutureTask
任务执行完成后,通过UNSAFE
设置waiters
相应的waitNode
为null,并通过LockSupport
类unpark
方法唤醒主线程;
这里我们编写测试方法来测试FutureTask
:
@Test
public void testFutureTask(){
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> future = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "future result";
}
});
try {
String result = future.get();
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
复制代码
运行结果:
在实际业务场景中,Future
和Callable
基本是成对出现的,Callable
负责产生结果,Future
负责获取结果。
Callable
接口类似于Runnable
,只是Runnable
没有返回值。
Callable
任务除了返回正常结果之外,如果发生异常,该异常也会被返回,即Future
可以拿到异步执行任务各种结果;
Future.get
方法会导致主线程阻塞,直到Callable
任务执行完成。
submit方法
submit
方法在ExecutorService
接口中的定义
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
复制代码
submit
方法在AbstractExecutorService
中的实现
// submit方法在AbstractExecutorService中的实现
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
// 通过submit方法提交的Callable任务会被封装成了一个FutureTask对象。
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
复制代码
AbstractExecutorService.submit()
实现了ExecutorService.submit()
可以获取执行完的返回值
通过submit
方法提交的Callable
任务会被封装成了一个FutureTask
对象。通过Executor.execute
方法提交FutureTask
到线程池中等待被执行,最终执行的是FutureTask
的run
方法;
FutureTask对象
内部状态
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
复制代码
内部状态的修改通过sun.misc.Unsafe
修改
get方法
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
复制代码
内部通过awaitDone
方法对主线程进行阻塞,具体实现如下:
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
复制代码
- 如果主线程被中断,则抛出中断异常;
- 判断
FutureTask
当前的state
,如果大于COMPLETING
,说明任务已经执行完成,则直接返回;
- 如果当前
state
等于COMPLETING
,说明任务已经执行完,这时主线程只需通过yield
方法让出CPU资源,等待state
变成NORMAL
;
- 通过
WaitNode
类封装当前线程,并通过UNSAFE
添加到waiters
链表;
- 最终通过
LockSupport
的park
或parkNanos
挂起线程;
run方法
public void run() {
if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, 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 must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
复制代码
FutureTask.run
方法是在线程池中被执行的,而非主线程
- 通过执行
Callable
任务的call
方法;
- 如果
call
执行成功,则通过set
方法保存结果;
- 如果
call
执行有异常,则通过setException
保存异常;
小结
本篇我们从线程池出发,讲了线程池的基本实现原理和池化思想。接着我们详细分析了ThreadPoolExecutor
包括它的部分源码,这个类是Java实现线程池的核心。
当然线程池的内容相当之多,本篇也只是谈了它的几个核心部分,其余的还是要再扩展学习。
本章参考: