从线程池说起
本文知识结构图:
线程池常用配置 & 源码理解
摘要:线程池常用配置以及相关使用方式。并基于源码理解其工作原理。在源码的基础上,延伸出来的相关知识点。
动态设定线程池关键参数
参考文章:
- https://www.cnblogs.com/thisiswhy/p/12690630.html
- https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html
关键信息:
- 线程池可以动态调整其核心数参数。
setCorePoolSize
方法 - spring
ThreadPoolTaskExecutor
类包装了 juc 的ThreadPoolExecutor
类,提供了更安全的调整线程池参数的方式
setCorePoolSize 源码及文档
/** * Sets the core number of threads. This overrides any value set * in the constructor. If the new value is smaller than the * current value, excess existing threads will be terminated when * they next become idle. If larger, new threads will, if needed, * be started to execute any queued tasks. * * @param corePoolSize the new core size * @throws IllegalArgumentException if {@code corePoolSize < 0} * @see #getCorePoolSize */ public void setCorePoolSize(int corePoolSize) { if (corePoolSize < 0) throw new IllegalArgumentException(); int delta = corePoolSize - this.corePoolSize; this.corePoolSize = corePoolSize; // 如果设置的 poolSize 小于当前工作的线程数,则将当前等待的任务取消(调用线程的 interrupt 方法) // 否则给当前队列中等待的任务创建新的线程(Worker),知道等待队列中没有任务在等待 if (workerCountOf(ctl.get()) > corePoolSize) interruptIdleWorkers(); else if (delta > 0) { // We don't really know how many new threads are "needed". // As a heuristic, prestart enough new workers (up to new // core size) to handle the current number of tasks in // queue, but stop if queue becomes empty while doing so. int k = Math.min(delta, workQueue.size()); while (k-- > 0 && addWorker(null, true)) { if (workQueue.isEmpty()) break; } } }
线程池的几个参数
使用 ThreadPoolExecutor 创建线程池。ThreadPoolExecutor 类是创建线程池的基本类,其他类似 Executors 的各种创建方法,实际都是都过 new ThreadPoolExecutor 对象来创建线程池的。
继承关系:
参数说明:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 10, // *corePoolSize: 保证存活的最少的线程数,最小是 0 20, // *maximumPoolSize: 线程池最大的线程数(最大值实际上取决于 CAPCITY: 2^29 - 1。具体解释如下 ) 0L, // keepAliveTime: 空闲线程的保留时间(毫秒)。当当前线程数量大于 corePoolSize 时,多出来的空余线程保留多久再 kill。在开启 allowCoreThreadTimeOut 时生效。否则一直保留空闲的线程 TimeUnit.SECONDS, // unit: 表示 keepAliveTime 的时间单位 new ArrayBlockingQueue<>(10) // workQuque: 存放等待的任务的队列 );
其中和线程池性能相关的参数有:
- corePoolSize
- maximumPoolSize
- workQuque(长度)
常用案例
定义一个简单的 Runnable 任务
Runnable PrintTask = () -> System.out.println(Thread.currentThread().getName());
向线程池提交一个任务(Runnable,Thread)
threadPoolExecutor.execute(PrintTask);
向线程池提交一个有结果的任务(Callable)
Future<String> submit = threadPoolExecutor.submit(() -> { System.out.println("after"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } return "Haha"; }); System.out.println("before"); Object ret = submit.get(); System.out.println(ret);
关闭线程池
// 不再接收新的任务提交,等现有任务(在运行的以及在队列中的)运行结束,关闭线程池 threadPoolExecutor.shutdown();
源码理解
线程池状态
线程池状态如下:
状态 | 描述 |
---|---|
RUNNGING | 能接受新提交的任务,并且也能处理阻塞队列中的任务 |
SHUTDONW | 关闭状态,不再接受新提交的任务,继续处理阻塞队列中的任务 |
STOP | 不接受新的任务,也不处理队列中的任务,并中断正在处理任务的进程 |
TIDYING | 所有任务都终止了,workerCount 为 0 |
TERMINATED | 在 terminated 方法执行完后进入该状态 |
线程池的状态维护,是内部通过两个值来维护的:workerCount
, runState
。具体实现,是将这两个值维护在一个变量里
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
ctl 使用 32 位整型的高三位保存 runState, 低 29 位保存 workerCount(最大就是 2^29 - 1 个)
private static final int COUNT_BITS = Integer.SIZE - 3; // 32 - 3 = 29 private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 运行状态保存在高位(一个符号位,两个数值位) // 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;
execute 解析
addWorker
shutdown 步骤
/** * Initiates an orderly shutdown in which previously submitted * tasks are executed, but no new tasks will be accepted. * Invocation has no additional effect if already shut down. * * <p>This method does not wait for previously submitted tasks to * complete execution. Use {@link #awaitTermination awaitTermination} * to do that. * * @throws SecurityException {@inheritDoc} */ public void shutdown() { // 1. 获取全局可重入锁 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 2. 权限检测,使用 SecurityManager 检测每个 worker 是否有 shutdown 权限。 checkShutdownAccess(); // 3. 调整线程池状态为 shutdown advanceRunState(SHUTDOWN); // 4. 将空闲的 worker 终止 interruptIdleWorkers(); // 5. 如果是调度的线程池,则可注册 shutdown 事件 onShutdown(); // hook for ScheduledThreadPoolExecutor } finally { mainLock.unlock(); } tryTerminate(); }
1. 权限检测
/** * If there is a security manager, makes sure caller has * permission to shut down threads in general (see shutdownPerm). * If this passes, additionally makes sure the caller is allowed * to interrupt each worker thread. This might not be true even if * first check passed, if the SecurityManager treats some threads * specially. */ private void checkShutdownAccess() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkPermission(shutdownPerm); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 依次检查当前调用者是否拥有每个 worker 所属线程的 shutdown 权限 for (Worker w : workers) security.checkAccess(w.thread); } finally { mainLock.unlock(); } } }
checkAccess 方法中的知识点。
通过查看 checkAccess 方法源码,可以知道,只有当传入的线程属于 rootGroup 时才进行 check。
public void checkAccess(Thread t) { if (t == null) { throw new NullPointerException("thread can't be null"); } if (t.getThreadGroup() == rootGroup) { checkPermission(SecurityConstants.MODIFY_THREAD_PERMISSION); } else { // just return } }
那么什么是 rootGroup 呢?
在该方法的注释中有这么一句: if the tread argument is a system thread then this method calls checkPermission. If the thread argument is not a system thread, this method just returns sliently。这句注释配合源码可以知道,rootGroup 就是系统线程。通过再次查看 rootGroup 的创建方式可以知道,所有线程组最终的 parent 是 system 这个线程组。
如果从 main 方法打断点,获取当前线程的 threadGroup,可以看到这样的一个线程组层级关系:
private static ThreadGroup rootGroup = getRootGroup(); private static ThreadGroup getRootGroup() { ThreadGroup root = Thread.currentThread().getThreadGroup(); while (root.getParent() != null) { root = root.getParent(); } return root; }
2. 升级线程池的状态
使用 CAS 无锁方式升级。
/** * Transitions runState to given target, or leaves it alone if * already at least the given target. * * @param targetState the desired state, either SHUTDOWN or STOP * (but not TIDYING or TERMINATED -- use tryTerminate for that) */ private void advanceRunState(int targetState) { for (;;) { int c = ctl.get(); if (runStateAtLeast(c, targetState) || ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)))) break; } }
那么什么是 CAS?
这里用到的是 AtomicInteger
的 compareAndSet
方法,该方法底层则是使用 Unsafe
类提供的 CAS 方法。
/** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * @param expect the expected value * @param update the new value * @return {@code true} if successful. False return indicates that * the actual value was not equal to the expected value. */ public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
CAS 是 Compare And Swap
的简称,用来原子性得修改变量值。原理是:修改值之前,判断变量当前值是否和期望的值一致,一致则说明该值没有被其他线程修改过,则进行修改;否则说明该值被其他线程修改过,本次修改失败。
因为 Unsafe 类的方法是 native 方法,实际使用的是 cpu 的原子指令 cmpxchg
。
CAS 参考文章:美团:Java 魔法类——Unsafe 应用解析
CAS 的典型应用场景:
- Java 的 atomic 相关类。
AtomicInterge
…… - Java juc 包下的 AQS
- CurrentHashMap
3. 中断空闲的 worker
实际执行的方法为:
/** * Interrupts threads that might be waiting for tasks (as indicated by not being locked) so they can check for termination or configuration changes. * Ignores SecurityExceptions (in which case some threads may remain uninterrupted). */ private void interruptIdleWorkers(boolean onlyOne) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) { Thread t = w.thread; // 如果线程不是已终止状态,并且能够获取到 worker 的锁(表示该 worker 空闲,未被加锁) if (!t.isInterrupted() && w.tryLock()) { try { t.interrupt(); } catch (SecurityException ignore) { } finally { w.unlock(); } } if (onlyOne) break; } } finally { mainLock.unlock(); } }
实际上,最终调用的还是线程的 interrupt
方法。
这里用尝试获取 worker 的锁来表示 worker 是否空闲。
那么,worker 的工作原理是什么呢
这里的 Worker 指的是 ThreadPoolExecutor
类中的 Worker 类。
- 功能:主要用来维护任务线程的中断控制状态。以及其他的记录工作
- 实现:继承了 AQS(
AbstractQueuedSynchronizer
) 来简化任务运行前后锁的获取与释放工作。
Worker 也实现了 Runnable 接口,也是一个可以运行的线程,其 run
方法中调用 ThreadPoolExecutor.runWorker
方法,该方法内部在获取到一个 task 之前,都会获取 Worker 的不可重入锁,表示当前 Worker 正在运行。(获取线程池状态时也会以此作为依据)
翻看 runWorker 的代码,可以发现以下几点值得注意的地方:
- Worker 被创建,如果一直可以获得任务,就会一直运行下去。使用 while 循环判断是否能获得任务,如果
getTask
返回 null, 则结束循环,销毁 worker。 - Worker 运行 task 的方式就是直接调用 task 的 run 方法。有点超出一般的认知。一般在用户代码中,不会直接调用 run 方法。普遍得认为,手动调用 run 方法会使这个线程失去被调度执行的机会,而 run 方法也将变成一个在当前线程执行的普通方法。但是在此处,Worker 本身就是被调度的,task 这个线程在此处的作用就是提供一个统一的 run 方法。
再来说说 getTask
方法。
getTask
- 功能:阻塞或者等待一段时间得获取 task
- 返回值情况:
- 正常情况下返回需要执行的 task
- 否则返回 null, 表示这个 worker 需要被结束。
- 当前 Worker 数超出
maximumPoolSize
- 线程池已经关闭(stopped)
- 线程池 shutdown 并且等待队列为空
- 当前 Worker 空闲的时间足够长(情况较复杂)
- 当前 Worker 数超出
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // 是否通过时间来判断 Worker 是否该被销毁 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { Runnable r = timed ? 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 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构