线程池工作原理
一、线程和进程的区别
一个线程只能归属于一个进程;
一个进程至少拥有一个线程。
二、线程池工作流程
创建一个线程池,核心线程数为2,最大线程数为5,非核心线程的空闲等待时间是10s, 等待队列使用ArrayBlockingQueue,饱和策略是AbortPolicy。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}corePoolSize:核心线程数。核心线程默认不超时。
maximumPoolSize:最大线程数。
keepAliveTime:非核心线程的空闲等待时间。临时线程执行完任务后,会主动去任务队列里获取任务。如果经过 keepAliveTime 没有获取到,临时线程会被销毁。
workQueue:等待队列。
handler:拒绝策略、饱和策略。
线程数与CPU核数的关系:最大线程数与CPU可同时处理的线程数相同。例如:4核CPU,8个逻辑处理器。那么设置maximumPoolSize=8, corePoolSize = 4.
提交一个任务到线程池中,线程池的处理流程如下:
- 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建),则创建一个新的工作线程来执行任务,否则进入下一个流程。
- 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在工作队列里。如果工作队列满了,则进入下一个流程。
- 判断线程池的线程是否处于工作状态,如果没有,则创建一个新的工作线程来执行任务,如果已经满了,则交给饱和策略来处理这个任务。
三、线程池状态之间的转换
状态 |
含义 |
---|---|
RUNNING |
线程池的初始化状态是RUNNING, 线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理 |
SHUTDOWN |
线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务,异步中断闲置的的线程,调用线程池的 shutdown() 接口时,线程池由RUNNING -> SHUTDOWN |
STOP |
线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。用线程池的 shutdownNow() 接口时,线程池由 (RUNNING or SHUTDOWN ) -> STOP |
TIDYING |
当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理,可以通过重载terminated()函数来实现。 当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING |
TERMINATED |
线程池彻底终止,就变成TERMINATED状态。 线程池处在TIDYING状态时,执行完 terminated()之后,就会由 TIDYING -> TERMINATED |
四、线程池的三种队列
- SynchronousQueue
- SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等待队列中的添加元素被消费后才能继续添加新的元素。
- 使用SynchronousQueue阻塞队列一般要求maximumPoolSize为无界,避免线程拒绝执行操作。
- LinkedBlockingQueue
- LinkedBlokingQueue是一个无界缓存等待队列,当前执行的线程数量达到corePoolSize数量时,剩余的元素会在阻塞队列里等待,(所以在使用此阻塞队列时maximumPoolSize就相当于无效了),每个线程完全独立于其他线程。生产者和消费者使用独立的锁来控制数据的同步,即在高并发的情况下可以并行操作队列中的数据。
- ArrayBlockingQueue
- ArrayBlockingQueue是一个有界缓存等待队列,可以指定缓存队列的大小。当正在执行的线程数等于corePoolSize时,多余的元素缓存在ArrayBlockingQueue队列中,等待有空闲的线程时继续执行。当ArrayBlockingQueue已满时,加入ArrayBlockingQueue失败,会开启新的线程去执行,当线程数已经达到最大的maximumPoolSize时,再有新的元素尝试加入ArrayBlockingQueue时会报错。
五、饱和策略
- AbortPolicy:抛出异常,丢弃任务。
- DiscardPolicy: 不抛出异常,丢弃最新的任务。
- DiscardOldestPolicy: 不抛出异常,丢弃旧的未执行的任务。
- CallerRunsPolicy: 不丢弃任务,调用线程池的线程(caller)帮忙执行任务。
六、Executors线程池的工具类(不推荐使用工具类)
Executors线程池的工具类提供了4种快捷创建线程池的办法。
- newCachedTreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,如果无可回收,则新建线程。可复用线程。
//核心线程数为0,不限制最大线程数。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } - newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
//指定线程数。核心线程数=最大线程数。 无空闲线程
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } - newSingleTreadPool:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
//核心线程数 = 最大线程数 = 1,无空闲时间
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } - newScheduledTreadPool:创建一个定长线程池,支持定时及周期性任务执行。
//创建一个Scheduled线程池,核心线程数是2 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2); //延迟执行。延迟时间4s scheduledExecutorService.schedule(new MyRunnable("name"), 4, TimeUnit.SECONDS); //延迟并周期性执行。延迟4s执行,之后每5s执行一次。 scheduledExecutorService.scheduleAtFixedRate(new MyRunnable("name2"), 4, 5, TimeUnit.SECONDS);
七、线程池的体系结构
java.util.concurrent.Executor 线程池的顶级接口,定义了线程池的最基本方法
java.util.concurrent.ExecutorService 定义常用方法
java.util.concurrent.ThreadPoolExecutor 线程池的核心实现类
java.util.concurrent.ScheduledThreadPoolExecutor
java.util.concurrent.Executors 线程池的工具类
八、线程池的作用分析
通过创建固定数量的线程来执行大量的任务,这样可以很好的复用线程,减少线程的创建和维护的时间消耗。
九、源码分析
名词解释:ctl
ctl 是一个打包两个概念字段的原子整数。
1)workerCount:指示线程的有效数量;
2)runState:指示线程池的运行状态,有 RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED 等状态。
int 类型有32位,其中 ctl 的低29为用于表示 workerCount,高3位用于表示 runState,如下图所示。
/** * 线程池控制状态ctl包含2个概念字段:workerCount,指有效的线程数量;runState,指线程池的状态。 * 为了将workerCount和runState用1个int来表示,我们限制workerCount范围为(2^29 - 1), * 即用int的低29位用来表示workerCount,用Int的高3位来标识runState。 * * 初始化时有效线程数为0,此时ctl为:1010 0000 0000 0000 0000 0000 0000 0000 */ private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); //高3位用来表示运行状态,此值用于运行状态向左移动的位数,即29位 private static final int COUNT_BITS = Integer.SIZE - 3; //线程数容量。低29位表示有效的线程数,0001 1111 1111 1111 1111 1111 1111 1111 private static final int CAPACITY = (1 << COUNT_BITS) - 1; /** * 大小关系:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED * 源码中频繁使用大小关系作为条件判断 * 1010 0000 0000 0000 0000 0000 0000 0000 运行 * 0000 0000 0000 0000 0000 0000 0000 0000 关闭 * 0010 0000 0000 0000 0000 0000 0000 0000 停止 * 0100 0000 0000 0000 0000 0000 0000 0000 整理 * 0110 0000 0000 0000 0000 0000 0000 0000 终止 */ 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;//终止 /** * 得到运行状态。入参为ctl的值 * ~CAPACITY的高3位全是1,低29位全是0, * 因此运算结果为ctl的高3位 */ private static int runStateOf(int c) { return c & ~CAPACITY; } /** * 得到有效线程数.入参为ctl的值 * CAPACITY的高3位全是0,低29位全是1, * 因此运算结果为ctl的低29位 */ private static int workerCountOf(int c) { return c & CAPACITY; }
CTL这么设计有什么优点?
- 主要好处是将对runState和workerCount的操作封装成一个原子操作。
- runState和workerCount是线程池正常运转的两个最重要属性。线程池在某一时刻该做什么,取决于这两个字段的值。
- 因此无论是查询还是修改,我们必须保证对这2个属性的操作是属于“同一时刻”的,也就是原子操作,否则就会出现错乱的情况。如果我们使用2个变量来分别存储,要保证原子性则需要额外进行加锁操作,这显然会带来额外的开销。而将这两个变量封装成一个AutomaticInteger,则不会带来额外的加锁开销,而且只需使用简单的位操作就能分别得到runState和workerCount。
/** * 存放池中的通过线程. 只有获取到主锁才能访问 */ private final HashSet<Worker> workers = new HashSet<Worker>();
//执行任务 public void execute(Runnable command) { if (command == null) throw new NullPointerException(); //主要作用:1、记录线程池的状态信息。2、记录线程池工作线程的数量 int c = ctl.get(); //workerCountOf(c) 工作中线程的数量 //流程1:如果当前工作中的线程数小于核心线程数,则新建一个核心工作线程 if (workerCountOf(c) < corePoolSize) { //添加一个Worker。参数1:要执行的任务。 参数2:true:添加核心线程。false:添加临时线程 if (addWorker(command, true)) return; c = ctl.get(); } //isRunning(c) 线程池是否运行状态 //流程2:尝试向阻塞队列中添加任务 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); } //流程3:添加一个Worker。参数1:要执行的任务。 参数2:true:添加核心线程。false:添加临时线程 else if (!addWorker(command, false)) //流程4:线程池已饱和,无法在创建新的Worker,执行饱和策略 reject(command); }
//创建Worker private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); ... for (;;) { //当前工作线程的数量 int wc = workerCountOf(c); if (wc >= CAPACITY || //core=true,判断当前工作数量是否大于等于corePoolSize,如果是,返回false. //core=false,判断当前工作数量是否大于等于maximumPoolSize,如果是,返回false. 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; ThreadPoolExecutor.Worker w = null; try { //通过构造器得到Worker对象 w = new ThreadPoolExecutor.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(); } //如果添加Worker成功 if (workerAdded) { //启动Worker中的线程,并执行Worker的run()方法 t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
//代表一个工作的线程 //Worker实现了Runnable接口,代表是一个可执行任务 private final class Worker extends AbstractQueuedSynchronizer implements Runnable { //具体工作的线程 final Thread thread; //第一次要执行的任务 Runnable firstTask; //构造器 Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker //创建worker时,传入第一次要执行的任务 this.firstTask = firstTask; //创建线程时传入this(当前Worker对象)。那么当线程启动时,就会执行Worker的run()方法。 this.thread = getThreadFactory().newThread(this); } //Worker的执行方法 public void run() { runWorker(this); } final void runWorker(ThreadPoolExecutor.Worker w) { Thread wt = Thread.currentThread(); //第一次要执行的任务 Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { //getTask()从阻塞队列中获取任务 //任务执行完毕后,会获取新的任务 while (task != null || (task = getTask()) != null) { w.lock(); ... try { beforeExecute(wt, task); Throwable thrown = null; try { //执行任务 task.run(); } ... } finally { //清空已经执行完的任务 task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } } //Worker线程启动后,会不断的使用getTask()方法获取任务执行 private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { //省略代码 try { //线程池中的工作线程的数量 int wc = workerCountOf(c); //如果核心线程允许使用keepAliveTime作为过期时间,或者工作线程数大于核心线程数 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; //在workerQueue队列中获取任务 Runnable r = timed ? //临时线程获取阻塞队列中的头部节点,超过keepAliveTime没有获取到任务就销毁 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : //核心线程阻塞式的获取阻塞队列中的头部节点 workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)