为什么使用线程池
如果不使用线程池,每一个任务都需要新开一个线程。我们可以通过创建固定数量的线程来进行任务的执行,从而避免的线程的创建和销毁会带来资源的消耗问题。
使用线程池的好处
- 加快响应速度
- 合理利用CPU和内存
- 线程的统一管理
线程池的适用场景
- 服务器接受到大量请求时,使用线程池技术是非常合适的,它可 以大大减少线程的创建和销毁次数,提高服务器的工作效率
- 实际上,在开发中,如果需要创建5个以上的线程,那么就可以使 用线程池来管理
创建和停止线程池
线程池构造函数的参数

corePoolSize:指的是核心线程数:线程池在完成初始化后,默认 情况下,线程池中并没有任何线程,线程池会等待有任务到来时 再创建新线程去执行任务maxPoolSize:线程池有可能会在核心线程数的基础上,额外增加一些线程,但 是这些新增加的线程数有一个上限
添加线程的规则
- 如果线程数小于corePoolSize ,即使其他工作线程处于 空闲状态,也会创建一个新线程来运行新任务。
- 如果线程数等于(或大于) corePoolSize但少于 maximumPoolSize , 则将任务放入队列。
- 如果队列已满,并且线程数小于maxPoolSize , 则创建 一个新线程来运行任务。
- 如果队列已满,并且线程数大于或等于maxPoolSize, 则拒绝该任务。

-
keepAliveTime:如果线程池当前的线程数多于corePoolSize ,那么如果多余的线程空闲时间超过keepAliveTime ,它们就会被终止 -
threadFactory:新的线程是由ThreadFactory创建的,默认使用Executors.defaultThreadFactory() , 创建出来的线程都在同一个线程组,拥有同样的NORM_ PRIORITY优先级并且都不是守护线程。如果自己指定ThreadFactory ,那么就可以改变 线程名、线程组、优先级、是否是守护线程等。
通常情况下使用默认的ThreadFactory就可以,但是也可以通过自定义的线程工厂来定义线程的名称等。
workQueue:用于存储线程池当前处理不了的任务(当前线程池的线程数量不超过最大线程数)
三种常见的队列类型
- 直接交接: SynchronousQueue(不存储任务,减少在队列中的中转)
- 无界队列: LinkedBlockingQueue
- 有界的队列: ArrayBlockingQueue
handler:当有界队列被填满之后,并且线程达到了最大线程数量,这时候就需要使用饱和策略
可以通过setRejectExecutionHandler来修改。
JDK提供了RejectExecutionHandler的四种实现方式,当然也可以自定义设置
- AbortPolicy();//默认,队列满了丢任务抛出异常
- DiscardPolicy();//队列满了丢任务不异常
- DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
- CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务
线程池应该手动创建还是自动创建
手动创建更好,因为这样可以让我们更加明确线程池的运行规则,避免资源耗尽的风险。
根据实际的情况进行创建更符合生产场景。
JDK提供的线程池
| 线程池名称 | 使用的工作队列 | 核心线程数 | 最大线程数 |
|---|---|---|---|
| FixedThreadPool | LinkedBlockingQueue | 自定义参数 | 与核心线程数相同 |
| SingleThreadExecutor | LinkedBlockingQueue | 1 | 1 |
| CachedThreadPool | SynchronousQueue | 0 | Integer.MAX_VALUE |
| ScheduledThreadPool | DelayedWorkQueue(根据任务的时间先后进行延迟) | 自定义参数 | Integer.MAX_VALUE |
-
FixedThreadPool (固定线程数量的线程池):
由于传进去的LinkedBlockingQueue是没有容量上限的 所以当请求数越来越多,并且无法及时处理完毕的时候, 也就是请求堆积的时候,会容易造成占用大量的内存,可 能会导致OOM。 -
SingleThreadExecutor(单线程的线程池,只会用唯一的工作线程来执行任务) 和上述的newFixedThreadPool的原理基本一样,只不过把线程数直接设置成了1 ,所以这也会导致 同样的问题,也就是当请求堆积的时候,可能会占用大量的内存。
-
CachedThreadPool(可缓存的线程池) 无界线程池,具有自动回收多余线程的功能。这里的弊端在于第二个参数maximumPoolSize被设置为了Integer.MAX_VALUE ,这可能会创建数量非常多的线程,甚至导致OOM。
-
ScheduledThreadPool(支持定时及周期性任务执行的线程池)
-
WorkStealingPool(工作窃取算法实现的线程池,JDK1.8加入) 和上面的几种线程池有很大的不同,这里面的任务是能够产生子任务的任务,例如:二叉树的遍历这种*递归的情形。此外,相对于上述的四种线程池这种线程池的线程之间是会合作的,如果有线程已经完成的自己的任务,会去拿其他未完成线程的任务队列(不是工作队列)的子任务来执行(所以任务队列的中子任务的执行顺序是不能保证的)。
正确创建线程池的方法
根据不同的业务场景,自己设置线程池参数,比如我们的内存有多大,我们想给线程取什么名字等等。
- CPU密集型(加密、计算hash等) : 最佳线程数为CPU核心 数的1-2倍左右。
- 耗时IO型(读写数据库、文件、网络读写等) : 最佳线程数一
般会大于cpu核心数很多倍,以JVM线程监控显示繁忙情况为
依据,保证线程空闲可以衔接上,参考Brain Goetz推荐的计算方法:
线程数=CPU核心数* ( 1+平均等待时间/平均工作时间)
停止线程池的正确方法
void shutdown():
启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。
boolean isShutdown():
如果此执行者已关闭,则返回 true 。
boolean isTerminated():
如果所有任务在关闭后完成,则返回 true 。 和isShutdown()不同的是如果执行者关闭而任务没完成则
isShutdown()返回true而isTerminated()返回false
boolean awaitTermination(long timeout, TimeUnit unit)
阻止所有任务在关闭请求完成后执行,或发生超时,或当前线程中断,以先到者为准。 简单点理解就是测试在一段时间内线程池是不是完全停止。
-
void terminated()执行程序已终止时调用方法。在ThreadPoolExecutor中没有具体实现 -
List<Runnable> shutdownNow()尝试停止所有主动执行的任务,停止等待任务的处理,并返回正在等待执行的任务列表。
任务太多,如何拒绝
拒绝的时机
-
当Executor关闭时,提交新任务会被拒绝。
-
以及当Executor对最大线程和I作队列容量使用有限边界并且已经饱和时。
4中拒绝策略
- AbortPolicy();//默认,队列满了丢任务抛出异常
- DiscardPolicy();//队列满了丢任务不异常
- DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
- CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务
- 自定义拒绝策略(例如,对任务进行持久化)
线程池原理和源码分析
线程池组成部分:
- 线程池管理器
- 工作线程.
- 任务列队
- 任务接口( Task )
Executor、ExecutorService、AbstractExecutorService、ThreadPoolExecutor的关系


而
Executors是一个工具类,主要用于创建不同的ThreadPoolExecutor
线程池实现任务复用的原理
- 相同的线程执行不同的任务
原理:由主线程循环获取任务,让线程池中的线程去执行
ThreadPoolExecutor.execute()源码
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
// 创建新线程执行任务,如果返回false,则加入阻塞队列
if (addWorker(command, true))
return;
c = ctl.get();
}
// 加入阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// Double Check,再次检查是否能够通过新线程创建成功
if (! isRunning(recheck) && remove(command))
reject(command);
// 没有线程了
else if (workerCountOf(recheck) == 0)
// 可能是workQueue中仍有未执行完成的任务,创建没有初始任务的worker线程执行
addWorker(null, false);
}
// 加入阻塞队列失败,先尝试加入启动一个新的线程执行该任务,调用RejectHandler中的方法进行处理
else if (!addWorker(command, false))
reject(command);
}
/**
* 就上述的整体流程而言
* 1.如果线程数量小于核心线程的数量,增加新的线程对任务进行处理
* 2.超过了线程数量,尝试加入阻塞队列,这一步需要进行Double Check,以及消息队列中任务的清理(如果线程数为0的话)
* 3.阻塞队列已满,尝试以非核心线程任务方法是增加Worker
*/
ThreadPoolExecutor.runWorker()源码
/**
* runWorker()的运行流程
*
* 1.根据worker获取要执行的任务task,然后调用unlock()方法释放锁,
* 这里释放锁的主要目的在于中断,因为在new Worker时,设置的state为-1,
* 调用unlock()方法可以将state设置为0,这里主要原因就在于interruptWorkers()
* 方法只有在state >= 0时才会执行;
* 2.通过getTask()获取执行的任务,调用task.run()执行,当然在执行之前会调用
* worker.lock()上锁,执行之后调用worker.unlock()放锁;
* 3.在任务执行前后,可以根据业务场景自定义beforeExecute() 和 afterExecute()方法,
* 则两个方法在ThreadPoolExecutor中是空实现;
* 4.如果线程执行完成,则会调用getTask()方法从阻塞队列中获取新任务,
* 如果阻塞队列为空,则根据是否超时来判断是否需要阻塞;
* 5.task == null或者抛出异常(beforeExecute()、task.run()、afterExecute()均有可能)
* 导致worker线程终止,则调用processWorkerExit()方法处理worker退出流程。
* @param w
*/
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
// 帮助GC?
w.firstTask = null;
// 释放锁,运行中断
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// task当前Worker的任务 -- 线程复用核心
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
// 确保只有当线程是stoping时,才会被设置为中断,否则清楚中断标示
// 如果线程池状态 >= STOP ,且当前线程没有设置中断状态,则wt.interrupt()
// 如果线程池状态 < STOP,但是线程已经中断了,再次判断线程池是否 >= STOP,如果是 wt.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 {
// 和beforeExecute同理
afterExecute(task, thrown);
}
} finally {
task = null;
// 完成任务数量+1
w.completedTasks++;
// 解锁
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 始终会调用
processWorkerExit(w, completedAbruptly);
}
}
线程池的状态
线程池中利用AtomicInteger变量ctl来存储线程池的状态
/**
* 变量ctl定义为AtomicInteger ,其功能非常强大,记录了“线程池中的任务数量”和“线程池的状态”两个信息。
* 共32位,其中高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;
/**
* RUNNING:处于RUNNING状态的线程池能够接受新任务,以及对新添加的任务进行处理。
*
* SHUTDOWN:处于SHUTDOWN状态的线程池不可以接受新任务,但是可以对已添加的任务进行处理。
*
* STOP:处于STOP状态的线程池不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
*
* TIDYING:当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,
* 若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
*
* TERMINATED:线程池彻底终止的状态。
*
* 新建,就绪,运行,阻塞,死亡 -- 线程的5状态
*/
// runState is stored in the high-order bits
/**
* RUNNING -- 对应的高3位值是111。
*/
private static final int RUNNING = -1 << COUNT_BITS;
/**
* SHUTDOWN -- 对应的高3位值是000。
*/
private static final int SHUTDOWN = 0 << COUNT_BITS;
/**
* STOP -- 对应的高3位值是001。
*/
private static final int STOP = 1 << COUNT_BITS;
/**
* TIDYING -- 对应的高3位值是010。
*/
private static final int TIDYING = 2 << COUNT_BITS;
/**
* TERMINATED -- 对应的高3位值是011。
*/
private static final int TERMINATED = 3 << COUNT_BITS;

实现线程池需要注意的地方
- 避免任务堆积
- 避免线程数过度增加
- 排查线程泄漏
线程执行完毕却不能被回收,任务执行可能存在问题
-参考课程:慕课网 - 玩转Java并发工具,精通JUC,成为并发多面手