一、线程池的实现原理
合理使用线程池能够带来3个好处
- 降低资源消耗:通过重复利用已创建的线程来降低线程创建和销毁造成的消耗。
- 提高相应速度:任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性:使用线程池对线程统一进行分配、调优和监控。
工作线程:线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会循环获取工作队列里的任务来执行。
1.1 线程池的处理流程
1.2 ThreadPoolExecutor 执行 execute() 方法
- 如果当前运行的线程少于
corePoolSize
,则创建新线程来执行任务,不论线程是否有空闲(执行这一步骤需要获取全局锁)。 - 如果运行的线程等于或多于
corePoolSize
,则将任务加入BlockingQueue
。 - 如果无法将任务加入
BlockingQueue
(队列已满),则创建新的线程来处理任务(执行这一步骤需要获取全局锁),超过corePoolSize
的线程在空闲时间超过keepAliveTime
后销毁。 - 当
ThreadPoolExecutor
已经关闭或ThreadPoolExecutor
已经饱和(达到了最大线程池且队列已满),任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()
方法。
执行execute()
方法时,尽可能避免获取全局锁。在ThreadPoolExecutor
完成预热之后(当前运行的线程数大于等于corePoolSize
),几乎所有的execute()
方法调用的都是步骤2,而步骤2不需要获取全局锁。
1.3 ThreadPoolExecutor 中线程执行任务
线程池中的线程执行任务分两种情况,如下。
- 在
execute()
方法中创建一个线程时,会让这个线程执行当前任务。 - 这个线程执行完上述任务后,会反复从
BlockingQueue
获取任务来执行。
二、线程池的使用
2.1 ThreadPoolExecutor 的参数
我们可以通过ThreadPoolExecutor
来创建一个线程池。
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
milliseconds,runnableTaskQueue, handler);
创建一个线程池时需要输入几个参数,如下。
- 1. corePoolSize(核心线程池的大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的
prestartAllCoreThreads()
方法,线程池会提前创建并启动所有基本线程。 - 2. BlockingQueue<Runnable> workQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列:
– ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
– LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue
。FixedThreadPool
和SingleThreadExecutor
使用了这个队列。
– SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue
,静态工厂方法Executors.newCachedThreadPool
使用了这个队列。
– PriorityBlockingQueue:一个具有优先级的无限阻塞队列。 - 3. maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务,线程执行完当前任务后会去队列中执行其他任务。值得注意的是,如果使用了无界的任务队列这个参数就没什么效果。
- 4. ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。使用开源框架guava提供的
ThreadFactoryBuilder
可以快速给线程池里的线程设置有意义的名字,代码如下。
–new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build();
- 5. RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,或者线程池不处于运行中(已经执行了ShutDown()方法),那么必须采取一种策略处理提交的新任务。这个策略默认情况下是
AbortPolicy
,表示无法处理新任务时抛出异常。在JDK 1.5中Java线程池框架提供了以下4种策略。
–AbortPolicy
:直接抛出异常。Executors
实现的创建的三种线程池都是这个饱和策略。
–CallerRunsPolicy
:只用调用者所在线程来运行任务。
–DiscardOldestPolicy
:丢弃队列里最近的一个任务,并执行当前任务。
–DiscardPolicy
:不处理,丢弃掉。
也可以根据应用场景需要来实现RejectedExecutionHandler
接口自定义策略。如记录日志或持久化存储不能处理的任务。 - 6. keepAliveTime(线程活动保持时间):当线程数超过
corePoolSize
且线程池的工作线程空闲时间超过keepAliveTime,则结束线程。如果线程数不大于corePoolSize
,空闲也不结束。 - 7. TimeUnit(时间单位):
keepAliveTime
参数的时间单位。
2.2 向线程池提交任务
可以使用两个方法向线程池提交任务,分别为execute()
和submit()
方法。
execute()
方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功,execute()
方法输入的任务是一个Runnable类的实例。
submit()
方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)
方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
2.3 关闭线程池
可以通过调用线程池的shutdown
或shutdownNow
方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别,shutdownNow
首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而shutdown
只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
只要调用了这两个关闭方法中的任意一个,isShutdown
方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed
方法会返回true。至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown
方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow
方法。
2.4 合理地配置线程池
CPU密集型任务应配置尽可能小的线程,如配置 N + 1
(N是CPU核心个数)个线程的线程池,IO密集型的任务配置 2 * N
。
2.5 线程池的监控
- taskCount:线程池需要执行的任务数量。
- completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。
- largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。
- getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销毁,所以这个大小只增不减。
- getActiveCount:获取活动的线程数。
通过扩展线程池进行监控。可以通过继承线程池来自定义线程池,重写线程池的
beforeExecute
、afterExecute
和terminated
方法,也可以在任务执行前、执行后和线程池关闭前执行一些代码来进行监控。例如,监控任务的平均执行时间、最大执行时间和最小执行时间等。
三、 Executor 框架
在 HotSpot VM 的线程模型中,Java线程被一对一映射为本地操作系统线程。
3.1 Executor 框架的结构
- 任务:包括被执行任务需要实现的接口:
Runnable
接口或Callable
接口。他们之间的区别是Runnable不会返回结果,而Callable可以返回结果。可以使用工厂类 Executors 把一个Runnable包装成一个Callable。 - 任务的执行:包括任务执行机制的核心接口
Executor
,以及继承自Executor
的ExecutorService
接口。Executor
框架有两个关键类实现了ExecutorService
接口(ThreadPoolExecutor
和ScheduledThreadPoolExecutor
)。 - 异步计算的结果:包括接口
Future
和实现Future
接口的FutureTask
类。
3.2 Executor 框架主要的类与接口
左上角的接口应为Runnable。
- Executor 接口在JUC包中,可以管理 Thread 对象,从而简化并发编程。Executor 将任务的提交与任务的执行分离开来。
- ExecutorService 接口继承自Executor 接口。相当于具有服务生命周期的Executor(例如关闭),知道如何构建恰当的上下文来执行Runnable对象。
- ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。
- ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。
ScheduledThreadPoolExecutor
比Timer
更灵活,功能更强大,并且Time是单个后台进程,前者可以是多个。 - Future接口和实现
Future
接口的FutureTask
类,代表异步计算的结果。 - shutdown() 方法防止新任务被提交给这个Executor,shutdown() 不会影响已经提交的线程任务
- shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。
可以把Runnable对象直接交给ExecutorService执行(ExecutorService.execute(Runnable command)
)。或者也可以把Runnable对象或Callable对象提交给ExecutorService执行(ExecutorService.submit(Runnable task)
或ExecutorService.submit(Callable<T>task)
)。
如果执行ExecutorService.submit(…)
,ExecutorService将返回一个实现Future接口的对象(到目前为止的JDK中,返回的是FutureTask对象)。由于FutureTask实现了Runnable,程序员也可以创建FutureTask,然后直接交给ExecutorService执行。最后,主线程可以执行FutureTask.get()
方法(会阻塞线程)来等待任务执行完成。主线程也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)
来取消此任务的执行。
四、ThreadPoolExecutor
ThreadPoolExecutor 通常使用工厂类 Executors 来创建,Executors 类 提供了3种基于ThreadPoolExecutor类构造线程池模型的方法:
- CachedThreadPool:通常一个任务创建一个新线程,多个线程同时进行,但如有线程执行完会复用。
- FixedThreadPool:所有任务只能使用固定大小的线程,初始化时指定线程池大小,重复使用线程。
- SingleThreadExecutor:相当于大小为 1 的 FixedThreadPool,如果向它提交了多个线程任务,这些任务将排队运行,所有任务使用相同的线程。
4.1 CachedThreadPool
CachedThreadPool 通常一个任务创建一个新线程,多个线程同时进行,但如有线程执行完会复用。
CachedThreadPool 是没有容量的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。
类定义:
这里用SynchronousQueue,队列里不存储值,线程空闲最多存活60秒。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
class PrintRunnable implements Runnable {
private static int i = 0;
@Override
public void run() {
System.out.println(i++);
}
}
public class CacheThreadPool {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
// 静态方法,生成 CachedThreadPool
for (int i = 0; i < 5; ++ i) {
// 这里创建5个线程,因为有5个任务
exec.execute(new PrintRunnable());
// 参数为实现Runnable接口的类
}
exec.shutdown();
// 作用是防止新任务被提交给这个Executor
// shutdown() 不会影响已经提交的线程任务
}
}
CachedThreadPool 的运行流程:
- 1)首先执行
SynchronousQueue.offer(Runnable task)
。如果当前maximumPool
中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)
,那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行,execute()
方法执行完成。否则执行下面的步骤。 - 2)当初始
maximumPool
为空,或者maximumPool
中当前没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)
。这种情况下,步骤1)将失败。此时CachedThreadPool
会创建一个新线程执行任务,execute()
方法执行完成。 - 3)在步骤2)中新创建的线程将任务执行完后,会执行
SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)
。这个poll操作会让空闲线程最多在SynchronousQueue
中等待60秒钟。如果60秒钟内主线程提交了一个新任务(主线程执行步骤1)),那么这个空闲线程将执行主线程提交的新任务。否则,这个空闲线程将终止。由于空闲60秒的空闲线程会被终止,因此长时间保持空闲的CachedThreadPool不会使用任何资源。
4.2 FixedThreadPool
FixedThreadPool适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。
类定义:
corePoolSize
与maximumPoolSize
数量相同,自己设置。keepAliveTime
为0。TimeUnit
是千分之一秒。workQueue
是LinkedBlockingQueue
,一个基于链表结构的阻塞队列,队列容量为Integer.MAX_VALUE
,称为无界队列。
// Executors.java
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// ThreadPoolExecutor.java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
FixedThreadPool 的运行流程:
- 如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。
- 在线程池完成预热之后(当前运行的线程数等于corePoolSize),将任务加入LinkedBlockingQueue。
- 线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。
类使用:
// 这里 FixedThreadPool 指定了容量为10的线程池,本例中先输出了0~9
// 约3秒后,其余的10个任务复用了之前的线程,输出了剩余的10~19。
class PrintRunnable implements Runnable {
private static int i = 0;
@Override
public void run() {
System.out.println(i++);
try {
Thread.sleep(3000);
} catch (Exception e) {
}
}
}
public class CacheThreadPool {
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(10);
// 静态方法,一次性生成大小为10的线程池
for (int i = 0; i < 20; ++ i) {
// 这里先运行10个线程,然后其余线程等待池中的线程空出再运行
exec.execute(new PrintRunnable());
// 参数为实现Runnable接口的类
}
exec.shutdown();
// 作用是防止新任务被提交给这个Executor
// shutdown() 不会影响已经提交的线程任务
}
}
4.3 SingleThreadExecutor
类定义:
相当于nThreads
为1的FixedThreadPool
。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
五、ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,通常使用工厂类 Executors 来创建,它主要用来在给定的延迟之后运行任务,或者定期执行任务。Executors可以创建2种类型的ScheduledThreadPoolExecutor:
- ScheduledThreadPoolExecutor:包含若干个线程的ScheduledThreadPoolExecutor,适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景。
- SingleThreadScheduledExecutor:只包含一个线程的ScheduledThreadPoolExecutor,适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的应用场景。
5.1 ScheduledThreadPoolExecutor 类定义
- 核心线程池的大小自己定义。
- 使用DelayQueue队列,无界(最多存储Integer.MAX_VALUE个任务)。
// J.U.C.ScheduledThreadPoolExecutor.java
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
// J.U.C.ThreadPoolExecutor.java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
5.2 ScheduledThreadPoolExecutor 的运行机制
ScheduledThreadPoolExecutor的执行主要分为两大部分:
- 当调用
ScheduledThreadPoolExecutor
的scheduleAtFixedRate()
方法或者scheduleWithFixedDelay()
方法时,会向ScheduledThreadPoolExecutor
的DelayQueue
添加一个实现了RunnableScheduledFuture
接口的ScheduledFutureTask
。 - 线程池中的线程从
DelayQueue
中获取ScheduledFutureTask
,然后执行任务。
DelayQueue 的排序:
前面我们提到过,ScheduledThreadPoolExecutor
会把待调度的任务(ScheduledFutureTask
)放到一个DelayQueue
中。ScheduledFutureTask
主要包含3个成员变量,如下。
- long型成员变量
time
,表示这个任务将要被执行的具体时间。 - long型成员变量
sequenceNumber
,表示这个任务被添加到ScheduledThreadPoolExecutor
中的序号。 - long型成员变量
period
,表示任务执行的间隔周期。
DelayQueue
封装了一个PriorityQueue
,这个PriorityQueue
会对队列中的ScheduledFutureTask
进行排序。排序时,time小的排在前面(时间早的任务将被先执行)。如果两个ScheduledFutureTask
的time相同,就比较sequenceNumber
,sequenceNumber
小的排在前面(也就是说,如果两个任务的执行时间相同,那么先提交的任务将被先执行)。
ScheduledThreadPoolExecutor 执行周期任务的过程:
- 线程1从
DelayQueue
中获取已到期的ScheduledFutureTask(DelayQueue.take())
。到期任务是指ScheduledFutureTask
的time
大于等于当时间。 - 线程1执行这个
ScheduledFutureTask
。 - 线程1修改
ScheduledFutureTask
的time
变量为下次将要被执行的时间。 - 线程1把这个修改
time
之后的ScheduledFutureTask
放回DelayQueue
中(DelayQueue.add()
)。
六、Future 接口
Future接口和实现Future接口的FutureTask类用来表示异步计算的结果。当我们把Runnable接口或Callable接口的实现类提交(submit)给ThreadPoolExecutor或ScheduledThreadPoolExecutor时,ThreadPoolExecutor或ScheduledThreadPoolExecutor会向我们返回一个FutureTask对象。
<T> Future<T> submit(Callable<T> task)
<T> Future<T> submit(Runnable task, T result)
Future<> submit(Runnable task)