1、为什么使用线程池
- 降低资源消耗
通过重复利用已创建的线程降低线程创建和销毁造成的消耗 - 提高响应速度
当任务到达时,任务可以不需要等到线程创建就能立即执行 - 提高线程的可管理性
使用线程池可以对线程进行统一分配、 管理和监控
3、创建线程池
使用ThreadPoolExecutor
类创建线程池,它的构造方法一共有7个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize: 核心线程数,线程池的在空闲情况下所能够保持的线程数。
当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了prestartAllCoreThreads()
方法,线程池会提前创建并启动所有基本线程。
maximumPoolSize: 线程池所能保存的最大线程数量
如果阻塞队列满了并且线程池已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。如果使用了无界的任务队列,则该参数无效
keepAliveTime: 线程的生存时间
当线程数大于corePoolSize
时,多余的空闲线程具有的生存时间,当线程在一定时间内处于空闲状态(没有执行任务),线程池会自动销毁该线程,但线程池不会销毁所有线程,而是保存最多corePoolSize
个线程。
TimeUnit: 线程生存时间的单位
BlockingQueue: 任务队列
用于保存等待执行的任务的阻塞队列。可选队列类型如下:
- ArrayBlockingQueue:基于数组的有界阻塞队列,此队列按FIFO(先进先出)原
则对元素进行排序。 - LinkedBlockingQueue:一个基于链表的阻塞队列,如果创建时设置了
size
参数就是有界队列,否则是无界队列。此队列按FIFO排序元素,吞吐量通常要高于数组队列。静态工厂方法Executors.newFixedThreadPool()
使用了这个队列。 - SynchronousQueue:存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked- BlockingQueue,静态工厂方法
Executors.newCachedThreadPool()
使用了这个队列。 - PriorityBlockingQueue: 具有优先级的无限阻塞队列。
ThreadFactory: 线程工厂
用于创建线程的工厂,可以在创建方法可以自定义线程的基本属性(如:名称、优先级、设置守护线程)
// 使用开源框架guava提供的ThreadFactoryBuilder创建线程工厂
new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build() ;
RejectedExecutionHandler: 饱和策略
当阻塞队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。在JDK中提供了以下4种策略:
- AbortPolicy:直接抛出异常(默认)
- DiscardPolicy:直接丢弃当前任务
- DiscardOldestPolicy:丢弃队列里最近的任务,并执行当前任务。
- CallerRunsPolicy:只用调用者所在线程来运行任务。
3、线程池工作流程
0)使用者向线程池提交了一个任务
1)如果当前运行的线程数少于corePoolSize
,则创建新线程来执行任务(创建线程需要获取全局锁)。
2)如果运行的线程数大于等于corePoolSize
,则将任务加入阻塞队列
3)如果阻塞队列已满,则创建新的线程来处理任务
4)如果创建新线程将使当前运行的线程超出最大线程数,则采取饱和策略处理
线程池采用上述步骤的总体设计思路,是为了在执行execute()方法时,尽可能地避免获取全局锁。在线程池完成预热之后(当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法调用都是执行步骤2,而步骤2不需要获取全局锁。
4、线程池的生命周期
调用线程池的shutdown()
或shutdownNow()
方法关闭线程池。
它们的原理是:遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以不能终止无法响应中断的任务。
shutdownNow:首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
shutdown:只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
5、线程池配置
要想合理地配置线程池,就必须首先分析任务特性,性质不同的任务用不同规模的线程池分开处理。可以从以下几个角度来分析:
- 任务的性质:CPU密集型任务、I0密集型任务和混合型任务
- 任务的优先级:高、中和低
- 任务的执行时间:长、中和短
- 任务的依赖性:是否依赖其他系统资源,如数据库连接
通过Runtime.getRuntime().availableProcessors()
方法获得当前设备的CPU个数
CPU密集型任务应配置尽可能小的线程,如配置N+1个线程的线程池
IO密集型任务的线程不是一直在执行任务(被IO阻塞),应配置尽可能多的线程,如:2*Ncpu
优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先执行。注意如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。
执行时间不同的任务可以交给不同规模的线程池来处理,或者可以使用优先级队列,让执行时间短的任务先执行。
依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,等待的时间越长,则CPU空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用CPU。
6、线程复用原理
线程池有一个内部类叫 Worker,它实现了 Runnable 接口,它有2个重要的内部变量 thread (创建的线程) 和 firstTask (线程的首任务)
Worker 的构造方法接收一个 Runnable 参数,将其赋值给 firstTask,还会使用线程池的线程工厂的 newThread(Runnable task)
方法创建线程,将自身作为参数传入。这样一来,当创建的线程执行时,就会执行 Worker对象 的 run()
方法,实际就是执行 runWorker()
方法。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
{
// 创建的线程
final Thread thread;
// 线程执行的第一个任务
Runnable firstTask;
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
}
在 runWorker()
中,当 Worker 对象的 firstTask 没有执行时,会执行它的 firstTask;若已执行,则会从阻塞队列中获取任务执行,这个过程会持续循环直到线程停止为止。至此就实现了线程的复用。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
try {
while (task != null || (task = getTask()) != null) {
...
task.run();
...
}
} finally {
processWorkerExit(w, completedAbruptly);
}
}
线程池调用execute
方法提交任务,通过addWorker()
创建 Worker 对象,并启动新建的线程
public void execute(Runnable command) {
// 如果当前线程数小于核心线程数,则创建新线程
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);
}
private boolean addWorker(Runnable firstTask, boolean core) {
.......
Worker w = null;
try {
// 创建 Worker
w = new Worker(firstTask);
// 获取 Worker 中的线程对象
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
........
// 运行线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
这是线程池execute()
方法的时序图,通过时序图便于理解上面的过程
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。