线程池学习
具体文章见: Java线程池实现原理及其在美团业务中的实践
线程池好处
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性
- 提供更多更强大的功能
线程池解决的问题
- 频繁申请、销毁资源和调度资源,将地阿莱额外的消耗,可能会非常巨大。
- 对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险。
- 系统无法合理管理内部的资源分布,降低稳定性。
线程池核心类
ThreadPoolExecutor
/**
* 一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务
*/
public class ThreadPoolExecutor extends AbstractExecutorService{}
/**
*将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可
*/ public abstract class AbstractExecutorService implements ExecutorService {}
//(1)扩充执行任务的能力,补充可以为一个或一批异步任务生成Future的方法;
//(2)提供了管控线程池的方法,比如停止线程池的运行。 public interface ExecutorService extends Executor {}
//Executor提供了一种思想:将任务提交和任务执行进行解耦。 public interface Executor {}
运行流程
- 任务部分
- 任务分配
- 直接执行
- 缓冲执行,放到阻塞队列里
- 任务拒绝
- 任务分配
- 线程池部分
- 核心线程数
- 非核心线程数
- 线程回收
- 执行任务
- 线程去任务缓冲取任务或直接执行被分配的任务
- 执行具体任务
源码大致梳理
顺序有点乱。中间execute是入口方法,下面是创建线程的方法。
- 线程数量
- 运行状态
1 /** 2 * ThreadPoolExecutor内的ctl 3 * 高3位保存runState,低29位保存workerCount,两个变量之间互不干扰 4 * 5 */ 6 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); 7 8 private static final int COUNT_BITS = Integer.SIZE - 3; 9 private static final int CAPACITY = (1 << COUNT_BITS) - 1; 10 11 12 //当前运行状态 取前三位状态 13 private static int runStateOf(int c) { return c & ~CAPACITY; } 14 //当前线程数量 取三位之后的状态 15 private static int workerCountOf(int c) { return c & CAPACITY; } 16 //生成ctl 根据状态和线程数 17 private static int ctlOf(int rs, int wc) { return rs | wc; }
初始化时 rs 传 -536870912 wc 传 0 ctl 就是 -536870912
Integer.SIZE - 3 = 29;
CAPACITY = 1 左移 29 位 , 为 536870912 , 二进制是 1000000000 0000000000 0000000000
~CAPACITY的作用 对所有位取非 ; 二进制是110 1111111111 1111111111 111111111
ThreadPoolExecutor五种状态
- RUNNING 能接受新任务,能处理阻塞队列任务
- SHUTDOWN 关闭状态,不在接受新任务,能处理阻塞队列任务
- shutdown()方法执行之后是这个状态
- STOP 不接受新任务
- shutdownNow()执行之后是这个
- TIDYING 所有的线程已经终止,workerCount为0
- TERMINATED 在terminated方法执行之后进入这个状态
- TIDYING状态执行terminated()是这个
1 //状态 2 private static final int RUNNING = -1 << COUNT_BITS; 3 4 private static final int SHUTDOWN = 0 << COUNT_BITS; 5 6 private static final int STOP = 1 << COUNT_BITS; 7 8 private static final int TIDYING = 2 << COUNT_BITS; 9 10 private static final int TERMINATED = 3 << COUNT_BITS; 11 12 13 //判断状态的方法 14 int c = ctl.get(); 15 private static boolean isRunning(int c) { 16 return c < SHUTDOWN; 17 } 18 19 20 final boolean isRunningOrShutdown(boolean shutdownOK) { 21 int rs = runStateOf(ctl.get()); 22 return rs == RUNNING || (rs == SHUTDOWN && shutdownOK); 23 }
增加线程的方法
// core true 核心线程 false 非核心线程 private boolean addWorker(Runnable firstTask, boolean core){}
retry: 用法
for (int i = 0 ; i < 3; i++){ a: for (int j = 0 ; j < 3; j++){ for (int k = 0 ; k < 3; k++){ System.out.println("ijk = " + i+":"+j+":"+k); if(k == 2){ break a; } } } } //输出 ijk = 0:0:0 ijk = 0:0:1 ijk = 0:0:2 ijk = 1:0:0 ijk = 1:0:1 ijk = 1:0:2 ijk = 2:0:0 ijk = 2:0:1 ijk = 2:0:2
a: for (int i = 0 ; i < 3; i++){ for (int j = 0 ; j < 3; j++){ for (int k = 0 ; k < 3; k++){ System.out.println("ijk = " + i+":"+j+":"+k); if(k == 2){ break a; } } } } //输出 ijk = 0:0:0 ijk = 0:0:1 ijk = 0:0:2
用来跳出多层for循环好。
threadPoolExecutor.execute(new ThreadPool());
首先,所有任务的调度都是由execute方法完成的,这部分完成的工作是:检查现在线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。
/** 1.如果运行的线程少于corePoolSize,请尝试以给定的命令作为第一个任务来启动一个新线程。
对addWorker的调用原子性地检查runState和workerCount,从而通过返回false来防止错误警报,这些错误警报会在不应该添加线程的情况下添加线程。 2.如果一个任务可以成功排队,那么我们仍然需要仔细检查我们是否应该添加一个线程(因为现有线程自上次检查以来已经死亡),
或者池自进入该方法以来已经关闭。因此,我们重新检查状态,如果有必要,如果停止,则回滚排队,如果没有,则启动一个新线程。(非核心线程) 3.如果我们不能对任务进行排队,那么我们尝试添加一个新线程。如果它失败了,我们就知道我们被关闭或饱和了 **/ public void execute(Runnable command) { if (command == null){ throw new NullPointerException(); } 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); } }
方法/处理方式 | 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 |
---|---|---|---|---|
插入方式 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除方法 | remove() | poll() | take() | poll(time.unit) |
检查方法 | element() | peek() | 不可用 | 不可用 |
一直阻塞 插入成功返回true,取不到元素则返回null
/** 检查是否可以根据当前池状态和给定绑定(核心或最大值)添加新的辅助进程。
如果是,工作人员计数会相应地进行调整,如果可能的话,会创建并启动一个新的工作人员,将firstTask作为其第一个任务运行。
如果池已停止或有资格关闭,此方法将返回false。如果线程工厂在被请求时未能创建线程,它也会返回false。
如果线程创建失败,无论是由于线程工厂返回null,还是由于异常(通常是thread.start()中的OutOfMemoryError),我们都会干净地回滚。 @param firstTask 新线程应该首先运行的任务(如果没有,则为null)。
当线程少于corePoolSize时(在这种情况下,我们总是启动一个线程),
或者当队列已满时(在那种情况下,必须绕过队列),会创建一个初始的第一个任务(在方法execute()中)来绕过排队。 最初的空闲线程通常是通过prestartCoreThread创建的,或者用来替换其他垂死的工作线程。 @param core如果为true,则使用corePoolSize作为绑定,否则 maximumPoolSize。 **/ private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); // 仅在必要时检查队列是否为空. 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(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
总结:(面试也可!常考)
首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
workQueue.offer(command) 加到该阻塞队列
用于容纳任务并将任务移交给工作线程的队列。我们不要求workQueue.poll()返回null就一定意味着workQueue.isEmpty(),所以只依赖isEmpty来查看队列是否为空(例如,在决定是否从SHUTDOWN转换为TIDYING时,我们必须这样做)。这适用于特殊用途的队列,如DelayQueues,允许poll()返回null,即使它稍后在延迟到期时可能返回非null。
private final BlockingQueue<Runnable> workQueue;
Worker线程管理
ThreadPoolExecutor里面有个内部类worker
private final class Worker extends AbstractQueuedSynchronizer implements Runnable { private static final long serialVersionUID = 6138294804551838833L; final Thread thread; Runnable firstTask; volatile long completedTasks; Worker(Runnable firstTask) { setState(-1); this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } public void run() { runWorker(this); } protected boolean isHeldExclusively() { return getState() != 0; } protected boolean tryAcquire(int unused) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } protected boolean tryRelease(int unused) { setExclusiveOwnerThread(null); setState(0); return true; } public void lock() { acquire(1); } public boolean tryLock() { return tryAcquire(1); } public void unlock() { release(1); } public boolean isLocked() { return isHeldExclusively(); } void interruptIfStarted() { Thread t; if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { t.interrupt(); } catch (SecurityException ignore) { } } } }
worker
实现了Runnable 里面的run方法 调用了runWorker() (这个runworker里 有线程自动回收的方法 非核心线程。 通过JVM回收
try {
while (task != null || (task = getTask()) != null) {
//执行任务
}
} finally {
processWorkerExit(w, completedAbruptly);//获取不到任务时,主动回收自己
}
)
继承了AQS
Worker是通过继承AQS,使用AQS来实现独占锁这个功能。没有使用可重入锁ReentrantLock,而是使用AQS,为的就是实现不可重入的特性去反应线程现在的执行状态。
lock方法一旦获取了独占锁,表示当前线程正在执行任务中。
如果正在执行任务,则不应该中断线程。
如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断。
线程池在执行shutdown方法或tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法会使用tryLock方法来判断线程池中的线程是否是空闲状态;如果线程是空闲状态则可以安全回收。
Worker线程增加
addWorker()
Worker线程回收
线程池中线程的销毁依赖JVM自动的回收
线程池在实际业务中的实践 的学习
1.查询商品信息
阻塞队列调小,或者不用阻塞队列,调高核心和非核心线程数。
2.离线计算,给运营导数据。
线程数不宜过多,cup核数+1就可以,避免上下文切换的开销。
线程池参数动态化?
corePoolSize、maximumPoolSize,workQueue
根据ThreadPoolExecutor里的几个public方法,设置相关参数。
拒绝策略
public interface RejectedExecutionHandler { void rejectedExecution(Runnable r, ThreadPoolExecutor executor); }
- AbortPolicy 丢弃并抛异常 默认策略
- DiscardPolicy 丢弃任务,不抛出异常
- DiscardOldestPolicy 丢弃队列最前面的
- CallerRunsPolicy 由调用线程执行
啊