线程池
池化技术的好处
- 降低资源消耗:可以重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
线程池的应用场景
- 服务器接受到大量请求时,使用线程池技术时非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率
- 实际上,在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理
线程池的类关系图
线程池的构造器参数
参数名 | 类型 | 含义 |
---|---|---|
corePoolSize | int | 核心线程数 |
maxPoolSize | int | 最大线程数 |
keepAliveTime | long | 保持存活时间 |
workQueue | BlockingQueue | 任务存储队列 |
threadFactory | ThreadFactory | 当线程池需要新的线程的时候,会使用threadFactory来生成新的线程 |
Handler | RejectedExecutionHandler | 由于线程池无法接受所提交的任务的拒绝策略 |
corePoolSize和maxPoolSize
corePoolSize
指的是核心线程数:线程池在完成初始化后,默认情况下,还没有创建任何线程,线程池会等待有任务到来时,再创建新线程去执行任务,直到达到核心线程数,之后核心线程会一直保持这个数量;当任务数量超过核心线程数,将任务放在阻塞队列workQueue
中,等待核心线程空闲后处理- 如果核心线程全部在工作中,而且队列也满了,线程池就会在核心线程的基础上,额外增加一些线程,这些新增加的线程数最大上限就是
maxPoolSize
线程创建规则
- 如果线程数小于corePoolSize, 即使其他线程处于空闲状态,也会创建一个新线程(核心线程)来运行新任务
- 如果线程数等于(或大于)corePoolSize但少于maxPoolSize,则将任务放入队列
- 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程来运行任务
- 如果队列已满,并且线程数大于或等于maxPoolSize则拒绝该任务
增减线程的特点
- 通过设置corePoolSize和maxPoolSize为相同数量,就可以创建固定大小的线程池,即使队列满了也不会在拓展线程
- 线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它,这就是队列的用意
- 通过设置maxPoolSize为很高的只,例如Integer.MAX_VALUE,可以允许线程池容纳任意数量的并发任务
- 是只有在队列填满时才创建多于corePoolSize的线程,所以如果使用无界队列(例如LinkedBlockingQueue),那么线程数就不会超过corePoolSize
keepAliveTime
空闲的非核心线程的存活时间,用于回收线程
- 如果线程池当前的线程数多于corePoolSize,那么如果多余的线程空闲时间超过keepAliveTime,它们就会被终止
ThreadFactory
线程工厂,用于创建线程
- 新的线程是由ThreadFactory创建的,默认使用的线程工厂是
Executors.defaultThreadFactory()
,创建出来的线程都在同一个线程组,拥有同样的NORM_PRIORITY
优先级并且都不是守护线程;如果自己定义ThreadFactory,那么就可以改变线程名,线程组,优先级,是否是守护线程等 - 通常使用默认的就可以,源码如下:
workQueue
有三种最常见的队列类型:
- 直接交接: SynchronousQueue 无容量
- 无界队列: LinkedBlockingQueue 无限容量,有内存溢出的风险
- 有界队列: ArrayBlockingQueue 可设置容量
ThreadPoolExecutor的启动
/**
* -通过 new 创建线程池时, 除非调用 prestartAllCoreThreads / prestartCoreThread 方法启动核心线程,
* -否则即使工作队列中存在任务,同样也不会执行.
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 20, 3L, TimeUnit.SECONDS, linkedBlockingDeque);
/**
* Starts all core threads, causing them to idly wait for work. This
* overrides the default policy of starting core threads only when
* new tasks are executed.
* -启动所有核心线程,让它们无空闲的等待工作。 这将覆盖仅在执行新任务时启动核心线程的默认策略。
* -手动启动线程池.
* @return the number of threads started
*/
threadPoolExecutor.prestartAllCoreThreads();
JDK内置线程池
线程池应该手动创建还是自动创建
手动创建,可以让我们更加明确线程池的允许规则,避免资源耗尽的风险
自动创建,也就是直接调用JDK封装号的构造函数,可能会带来一些问题:
Executors.newFixedThreadPool(int nThreads)
数量固定的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
corePoolSize和maxPoolSize被设置为相同的nThreads参数,并使用了无界队列LinkedBlockingQueue,不会拓展线程所以也没有存活时间
当任务在队列中堆积过多,可能就会造成OOM
Executors.newSingleThreadExecutor()
只有一个线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
Executors.newCachedThreadPool()
可缓存线程
无界线程池,具有自动回收多余线程的功能
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
最大的线程数被设置为Integer.MAX_VALUE,线程空闲60秒后回收,不使用队列(SynchronousQueue)
Executors.newScheduledTreadPool()
支持定时及周期性任务执行的线程池,使用延迟队列(DelayedWorkQueue)
public static void main(String[] args) {
ScheduledExecutorService threadPool =
Executors.newScheduledThreadPool(10);
//延迟5秒执行任务
threadPool.schedule(new EveryTaskOneThread.Task(),5, TimeUnit.SECONDS);
//1秒之后每个3秒执行一次任务
threadPool.scheduleAtFixedRate(new EveryTaskOneThread.Task(),
1, 3,TimeUnit.SECONDS);
}
所以,还是更具业务的并发量手动创建线程池吧
JDK1.8后加入workStealingPool
- 子任务
- 窃取
线程数量怎么设定?
- CPU密集型(加密,即使hash等) : 最佳线程数为CPU核心数的1-2倍左右
- 耗时I/O型(读写数据库,文件,网络传输等): 最佳线程数一般会大于CPU核心数很多倍,以JVM线程监控显示繁忙情况为依据,保证线程空闲可以衔接上,参考Brain Goetz推荐的计算方法:
==线程数=CPU核心数 * (1+平均等待时间/平均工作时间))==
- 实际上最靠谱的还是通过压力测试得出合适的线程数
停止线程池的正确方式
- shutdown 执行该方法后,线程池会将当前队列中的任务执行完毕,并且在次期间拒绝新任务进入,执行完后停止线程池
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++){
executorService.execute(new ShutDownTask());
}
System.out.println(executorService.isShutdown());
Thread.sleep(1500);
executorService.shutdown();
//是否进入停止状态
System.out.println(executorService.isShutdown());
//拒绝新任务
executorService.execute(new ShutDownTask());
//是否真正意义上的关闭
System.out.println(executorService.isTerminated());
}
static class ShutDownTask implements Runnable {
@Override
public void run() {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
awaitTermination(timeout)
:在一段时间内所有任务是否被执行完毕
- shutdownNow 将所有线程中断,并且队列中还未执行的任务作为一个列表返回
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++){
executorService.execute(new ShutDownTask());
}
System.out.println(executorService.isShutdown());
Thread.sleep(1500);
//发送中断信号,并返回runnableList
List<Runnable> runnableList =
executorService.shutdownNow();
}
static class ShutDownTask implements Runnable {
@Override
public void run() {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "被中断了");
}
}
}
任务拒绝策略
拒绝时机
- 当Executor关闭(shutdown)时,提交新任务会被拒绝
- 当Executor对最大线程和队列容量使用有限制并且已经饱和时
4种拒绝策略
- AbortPolicy: 默认,直接抛出RejectedExecutionException拒绝异常
- DiscardPolicy: 默默的把被拒绝的任务丢弃
- DiscardOldestPolicy: 当有新任务时,会丢弃任务队列中存在最久的老任务,以腾出位置给新任务
- CallerRunsPolicy: 将被线程池拒绝的任务交给调用者(caller)主线程去执行
钩子方法
每个任务执行前后可以增加处理(日志,统计)
/**
* 演示每个任务执行前后都可以放钩子函数
*/
public class PauseableTreadPool extends ThreadPoolExecutor {
private final ReentrantLock lock = new ReentrantLock();
private Condition unpaused = lock.newCondition();
//标记线程是否处于暂停状态
private boolean isPaused;
public PauseableTreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public PauseableTreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}
public PauseableTreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
public PauseableTreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
//重写方法 before钩子
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
lock.lock();
try {
while(isPaused) {
unpaused.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
//暂停方法
private void pause() {
lock.lock();
try {
isPaused = true;
} finally {
lock.unlock();
}
}
//恢复方法
private void resume() {
lock.lock();
try{
isPaused = false;
//唤醒全部
unpaused.signalAll();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
PauseableTreadPool pauseableTreadPool =
new PauseableTreadPool(10, 20,
10l, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
Runnable runnable = new Runnable() { //线程体
@Override
public void run() {
System.out.println("被执行");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 10000; i++){
pauseableTreadPool.execute(runnable);
}
Thread.sleep(1500);
pauseableTreadPool.pause();
System.out.println("线程池被暂停");
Thread.sleep(1500);
pauseableTreadPool.resume();
System.out.println("线程池已恢复");
}
}
原理&源码分析
主要分析ThreadPoolExecutor
线程池的组成部分
- 线程池管理器 ExecutorService控制线程池的启动和停止
- 工作线程 ThreadPoolExecutor中的内部类Worker
- 任务队列 线程安全的
BlockingQueue<Runnable> workQueue;
任务接口(Task)
/** * Creates with given first task and thread from ThreadFactory.
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
**线程池事项任务复用的原理**
- 用相同的线程执行不同的任务
**ThreadPoolExecutor中的execute方法**
/**
* 在将来的某个时间执行给定的任务,任务可以在新线程或池中现有的线程中执行
* 如果无法将任务提交执行,原因之一是执行器已关闭或由于其容量已满,该任务由当前的 RejectedExecutionHandler 处理
* @param command 要执行的任务
*
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. 如果工作线程数量少于corePoolSize,尝试调用addWorker以给定的command启动一个新线程
*
* 2.如果一个任务可以成功排队,那么我们仍然需要
* 仔细检查我们是否应该添加线程
* (因为现有的自上次检查后死亡)或
* 自从进入此方法以来,该池已关闭。 所以我们
* 重新检查状态,并在必要时回退排队
* 停止,如果没有,则启动一个新线程。
*
* 3.如果我们无法将任务排队,那么我们尝试添加一个新的
* 线程。 如果失败,我们知道我们已经关闭或饱和
* 并因此拒绝任务。
*/
int c = ctl.get(); //ctl记录了线程池状态和线程数
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);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try { //循环获取任务执行
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
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 {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
## 线程池的状态
- RUNNING: 接受型任务并处理排队任务
- SHUTDOWN: 不接受任务,但处理排队任务
- STOP: 不接受新任务,也不处理排队任务,并中断正在进行的任务
- TIDYING(整洁): 所有任务都已终止, workerCount为0时,线程会转换到TIDYING状态, 并将运行 terminate() 钩子方法
- TERMINATED: terminate() 运行完成
// runState is stored in the high-order bits
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;
## 使用线程池的注意点
- 避免任务的堆积
FixedThreadPool SingleThreadExecutor
任务队列长度过大, 可能会堆积大量的请求, 从而导致OOM.
- 避免线程数过度增加
CachedThreadPool ScheduledThreadPool
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
- 排查线程泄漏
线程已经执行完毕,却没有正确的被回收,往往是任务的逻辑问题
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。