Java 基础体系 · 第 62/100 篇。示例统一以 Java 25 LTS 为语言和 JVM 基线;框架示例使用与其兼容的现代稳定版本。
Java 线程生命周期:创建、状态、Interrupt、守护线程和协作退出
线程生命周期描述的是一个 Thread 对象从创建、启动、执行,到阻塞、等待,最后结束的全过程。理解这个过程不能只记住几个状态名称,还要区分:
Thread对象是否已经启动;- 线程当前处于哪一种 JVM 可观察状态;
interrupt是请求而不是强制终止;- 守护线程如何影响 JVM 退出;
- 线程如何通过共享状态和中断实现可控的协作退出。
本文以 Java 25 LTS 的 java.lang.Thread 模型为范围,同时说明平台线程和虚拟线程在生命周期管理上的共同点与差异。
一、从 Thread 对象到真正执行
1. Thread 对象不等于已经运行的线程
创建一个 Thread 对象,只是创建了一个 Java 对象,并不会立即创建对应的执行过程:
Thread thread = new Thread(() -> {
System.out.println("worker is running");
});
System.out.println(thread.getState()); // NEW
此时线程状态是 NEW。Runnable 对象已经保存在线程对象中,但线程体尚未执行。
调用 start() 后,JVM 才会安排该线程执行:
thread.start();
start() 的主要语义是:
- 将线程从
NEW转入可运行状态; - 创建或启动与该
Thread对象关联的执行过程; - 使该执行过程最终调用线程的
run()方法。
线程何时真正获得 CPU 由线程调度器决定,调用 start() 的线程不会因此等待新线程执行完毕。
2. start() 和 run() 完全不同
直接调用 run() 不会启动新线程:
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
});
thread.run();
这里的输出通常是调用方线程的名称,例如 main。run() 只是普通方法调用。
而:
thread.start();
才会让线程体在线程 thread 中执行。
一个 Thread 对象只能成功调用一次 start():
thread.start();
thread.start(); // 抛出 IllegalThreadStateException
线程执行完毕后不能重新回到 NEW,也不能通过再次调用 start() 复用。需要再次执行同一段逻辑时,应创建新的 Thread 对象,或者使用线程池、执行器等更高层抽象。
3. Java 25 中的平台线程和虚拟线程
Java 25 中可以使用传统的平台线程:
Thread platformThread = Thread.ofPlatform()
.name("platform-worker")
.unstarted(() -> {
System.out.println("platform thread");
});
platformThread.start();
也可以使用虚拟线程:
Thread virtualThread = Thread.ofVirtual()
.name("virtual-worker")
.unstarted(() -> {
System.out.println("virtual thread");
});
virtualThread.start();
或者直接启动一个虚拟线程:
Thread virtualThread = Thread.startVirtualThread(() -> {
System.out.println("virtual thread");
});
平台线程通常对应操作系统线程;虚拟线程由 JVM 调度,并在阻塞时可以卸载其承载关系。两者都具有 Thread.State、interrupt()、join() 等生命周期 API,因此本文讨论的状态和协作退出原则同样适用。
但虚拟线程有一个重要差异:虚拟线程始终是守护线程,不能转换为非守护线程。需要保证任务完成时,不能只依靠虚拟线程本身阻止 JVM 退出,而应通过 join()、执行器生命周期管理或其他明确的协调机制等待它完成。
二、Thread.State 表示什么
Thread.State 定义了六种 JVM 线程状态:
public enum Thread.State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED
}
这些状态是 JVM 对线程状态的抽象,不是操作系统调度器的完整状态集合。特别是,RUNNABLE 同时包含“正在运行”和“已经具备运行条件、等待 CPU”的情况。
典型状态转换如下:
stateDiagram-v2
[*] --> NEW: new Thread()
NEW --> RUNNABLE: start()
RUNNABLE --> BLOCKED: 等待进入 synchronized 监视器
BLOCKED --> RUNNABLE: 获得监视器
RUNNABLE --> WAITING: wait()/join()/park()
WAITING --> RUNNABLE: 被通知、目标结束或 unpark
RUNNABLE --> TIMED_WAITING: sleep()/wait(timeout)/join(timeout)
TIMED_WAITING --> RUNNABLE: 超时、被通知或中断
RUNNABLE --> TERMINATED: run() 返回或异常退出
BLOCKED --> TERMINATED: 取得锁后继续执行并退出
WAITING --> TERMINATED: 恢复执行后退出
TIMED_WAITING --> TERMINATED: 恢复执行后退出
图中的转换不是任意 API 都能产生任意状态。例如,调用 Thread.sleep() 会进入 TIMED_WAITING,不会进入 WAITING;等待 synchronized 的监视器进入 BLOCKED,而等待 ReentrantLock 通常表现为 WAITING 或 TIMED_WAITING,因为它不是 JVM 内置监视器锁。
1. NEW
线程对象已经创建,但还没有调用成功的 start():
Thread t = new Thread(() -> {});
System.out.println(t.getState()); // NEW
构造线程、设置名称、设置守护属性、设置未捕获异常处理器,都通常应在 start() 之前完成。
2. RUNNABLE
RUNNABLE 表示线程正在 JVM 中执行,或者已经可以执行但尚未获得 CPU。它不保证线程此刻正在运行。
例如:
Thread t = new Thread(() -> {
while (true) {
// 持续执行
}
});
t.start();
System.out.println(t.getState()); // 通常是 RUNNABLE
即使 getState() 返回 RUNNABLE,线程也可能正处于操作系统调度等待、JIT 编译、JNI 调用等实现相关阶段。不能用它精确判断“此刻占用了 CPU”。
3. BLOCKED
BLOCKED 专门表示线程正在等待进入某个 synchronized 监视器:
final Object lock = new Object();
Thread holder = new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
Thread waiter = new Thread(() -> {
synchronized (lock) {
System.out.println("获得锁");
}
});
holder.start();
Thread.sleep(100);
waiter.start();
System.out.println(waiter.getState()); // 可能是 BLOCKED
waiter 已经执行到 synchronized (lock),但 holder 仍然持有 lock,所以它无法进入临界区。
BLOCKED 不表示线程正在执行临界区,也不表示线程在调用 Object.wait()。前者是持有锁后的 RUNNABLE,后者通常是 WAITING 或 TIMED_WAITING。
4. WAITING
WAITING 表示线程无限期等待某个动作,常见来源包括:
Object.wait();Thread.join();LockSupport.park();- 某些同步器的无超时等待。
例如:
Thread worker = new Thread(() -> {
synchronized (Thread.currentThread()) {
try {
Thread.currentThread().wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
worker.start();
Thread.sleep(100);
System.out.println(worker.getState()); // 通常是 WAITING
worker.interrupt();
worker.join();
调用 wait() 时,线程会释放所持有的该对象监视器,并等待通知、中断或其他规定的唤醒条件。线程被唤醒后,还必须重新竞争监视器,才能从 wait() 返回。
5. TIMED_WAITING
TIMED_WAITING 表示线程等待一个有时间限制的事件,例如:
Thread.sleep(long);Object.wait(long);Thread.join(long);LockSupport.parkNanos();- 某些同步器的限时等待。
Thread worker = new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
worker.start();
Thread.sleep(100);
System.out.println(worker.getState()); // 通常是 TIMED_WAITING
worker.interrupt();
worker.join();
超时只是使等待条件失效,不代表线程已经结束。线程通常会回到 RUNNABLE,继续执行后面的代码。
6. TERMINATED
当 run() 方法正常返回,或者因未捕获异常退出时,线程进入 TERMINATED:
Thread t = new Thread(() -> {
throw new RuntimeException("failure");
});
t.start();
t.join();
System.out.println(t.getState()); // TERMINATED
异常不会自动传播到调用 start() 的线程。异常会由该线程的未捕获异常处理器处理;如果没有自定义处理器,则使用线程组或默认处理器。
TERMINATED 是终态,不能重新启动。
7. getState() 是瞬时观察
getState() 返回的是调用时刻的近似快照,状态可能在读取后立即变化:
Thread t = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
t.start();
System.out.println(t.getState()); // 可能是 RUNNABLE,也可能已是 TIMED_WAITING
因此不能编写依赖精确时序的逻辑,例如:
if (t.getState() == Thread.State.WAITING) {
// 不能据此断言下一条语句中线程仍然在等待
}
线程诊断工具可以使用这些状态定位问题,但业务同步应使用锁、队列、CountDownLatch、Future 等具有明确同步语义的机制。
三、线程生命周期中的 happens-before 关系
线程生命周期还涉及内存可见性。Java 内存模型提供了两个非常重要的关系。
1. start() 之前的动作先于新线程中的动作
如果线程 A 在调用 thread.start() 之前写入变量,那么线程 B 开始执行后可以看到这些写入:
class Task implements Runnable {
private int value;
void setValue(int value) {
this.value = value;
}
@Override
public void run() {
System.out.println(value);
}
}
Task task = new Task();
task.setValue(42);
Thread t = new Thread(task);
t.start();
这里构造线程和设置 value 的动作发生在 start() 之前,因此这些动作与新线程中的执行之间存在 start 相关的 happens-before 关系。
2. 线程结束先于成功的 join() 返回
如果工作线程在结束前写入结果,另一个线程成功返回 join() 后可以读取这些结果:
class Result {
int value;
}
Result result = new Result();
Thread t = new Thread(() -> {
result.value = 42;
});
t.start();
t.join();
System.out.println(result.value); // 42
join() 不只是“等待一段时间”,它还提供了线程终止与调用方后续动作之间的同步关系。
如果只轮询一个普通非 volatile 字段而不使用 join()、锁或其他同步机制,则不能仅凭代码表面推断调用方一定能看到最新值。
四、Interrupt:中断是请求,不是强制杀线程
1. Interrupt 的基本语义
interrupt 是一个线程向另一个线程发出的协作信号,表示:
如果你正在等待,或者你的任务允许取消,请尽快停止当前等待或工作。
调用:
target.interrupt();
并不等价于:
立即终止 target
释放 target 持有的所有锁
回滚 target 已经完成的操作
强制跳出任意方法
Java 没有提供一个安全的“从外部立即杀死线程”的通用机制。历史上的 Thread.stop()、suspend()、resume() 存在状态破坏或死锁风险,已经被弃用,不应作为正常生命周期管理手段。
2. 中断状态
每个线程都有一个中断状态。可以使用:
boolean interrupted = Thread.currentThread().isInterrupted();
查询当前线程或目标线程的状态:
boolean interrupted = thread.isInterrupted();
isInterrupted() 只查询,不清除状态。
静态方法:
boolean interrupted = Thread.interrupted();
查询当前线程的中断状态,并在返回后清除该状态。因此下面两次结果通常不同:
Thread.currentThread().interrupt();
System.out.println(Thread.currentThread().isInterrupted()); // true
System.out.println(Thread.interrupted()); // true,并清除
System.out.println(Thread.currentThread().isInterrupted()); // false
Thread.interrupted() 会修改状态,调用它前应明确自己是否确实要消费这个中断信号。
3. 阻塞方法如何响应中断
一些可中断阻塞方法收到中断后,会:
- 清除当前线程的中断状态;
- 抛出
InterruptedException。
典型方法包括:
Thread.sleep(...);Object.wait(...);Thread.join(...);BlockingQueue的阻塞操作;Lock.lockInterruptibly();Condition.await(...)。
示例:
try {
Thread.sleep(10_000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().isInterrupted());
// 通常输出 false
}
异常被捕获时,中断状态通常已经被清除。因此如果当前方法不能继续处理这个中断,通常应恢复状态:
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
恢复状态的作用是把“中断请求尚未被上层处理”这一事实传递给调用方或更外层的代码。
4. 并非所有等待都以 InterruptedException 结束
LockSupport.park() 被中断时会返回,但不会像 sleep() 那样自动清除中断状态:
LockSupport.park();
if (Thread.currentThread().isInterrupted()) {
// 仍可能为 true
}
使用 NIO 中断通道时,具体行为由通道 API 规定,某些情况下会关闭通道并抛出 ClosedByInterruptException。因此不能把所有中断响应都概括成“统一抛出 InterruptedException”。
更重要的是,正在执行普通 CPU 计算的线程不会因为被中断而自动抛异常:
Thread worker = new Thread(() -> {
long sum = 0;
for (long i = 0; i < Long.MAX_VALUE; i++) {
sum += i;
}
});
如果该循环没有检查中断状态,即使其他线程调用 worker.interrupt(),它仍可能继续计算到结束。
5. 中断 CPU 密集型循环
CPU 密集型任务必须主动检查中断:
Thread worker = new Thread(() -> {
long sum = 0;
for (long i = 0; i < Long.MAX_VALUE; i++) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("收到取消请求");
return;
}
sum += i;
}
System.out.println(sum);
});
这里 isInterrupted() 不清除状态,所以其他代码仍能观察到该中断请求。
也可以使用:
if (Thread.interrupted()) {
return;
}
但这会清除中断状态。除非当前代码明确消费了这个信号,否则更适合使用 isInterrupted()。
五、守护线程与 JVM 退出
1. 守护线程的定义
守护属性是线程的一个属性:
Thread thread = new Thread(() -> {
// background work
});
thread.setDaemon(true);
thread.start();
必须在 start() 之前设置:
thread.start();
thread.setDaemon(true); // 抛出 IllegalThreadStateException
新线程默认继承创建它的线程的守护属性。主线程通常是非守护线程,因此主线程创建的普通线程默认也是非守护线程。
2. JVM 何时退出
当所有已经启动的非守护线程都结束后,JVM 会开始退出。仍然运行的守护线程不会阻止这一过程。
public class DaemonDemo {
public static void main(String[] args) throws Exception {
Thread daemon = Thread.ofPlatform()
.daemon()
.name("daemon-worker")
.start(() -> {
try {
Thread.sleep(10_000);
System.out.println("daemon finished");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread.sleep(100);
System.out.println("main finished");
}
}
程序可能只输出:
main finished
daemon finished 不保证出现,因为 main 结束后已经没有非守护线程,JVM 可以结束进程。
守护线程不是“优先级较低的线程”,也不是“自动安全退出的线程”。它只是不会阻止 JVM 结束。
3. 守护线程的资源风险
JVM 退出时,守护线程可能来不及:
- 执行完整的
finally; - 刷新缓冲区;
- 提交最后一批数据;
- 关闭网络连接;
- 完成文件写入;
- 通知其他组件。
因此不能把必须完成的任务仅交给守护线程,例如订单落库、日志持久化、消息确认、事务提交等。
守护线程适合承载“进程退出时可以放弃”的辅助工作,例如缓存刷新、定期统计或诊断轮询,但即使如此,也应明确任务被放弃后的数据和资源影响。
虚拟线程始终是守护线程,所以使用虚拟线程时尤其不能把“虚拟线程还在运行”当作“JVM 会继续运行”的依据。
六、协作退出:状态通知与中断唤醒必须配合
1. 为什么只使用一个停止标志不够
下面的代码存在退出延迟:
class Worker implements Runnable {
volatile boolean stopping;
@Override
public void run() {
while (!stopping) {
try {
Thread.sleep(10_000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
stopping = true 可以被线程看到,因为字段是 volatile。但是如果工作线程此刻正在 sleep(10_000),它可能要等最长十秒才再次检查 stopping。
因此,停止请求通常由两部分组成:
- 写入一个可见的停止状态,表达“任务不应继续”;
- 调用
interrupt(),唤醒正在等待的线程。
2. 一个完整的协作退出示例
下面的程序可以直接保存为 ThreadLifecycleDemo.java,使用 Java 25 编译运行:
public class ThreadLifecycleDemo {
static final class Worker implements Runnable {
private volatile boolean stopping;
void requestStop(Thread thread) {
stopping = true;
thread.interrupt();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " started");
try {
while (!stopping) {
System.out.println("working...");
Thread.sleep(1_000);
}
} catch (InterruptedException e) {
/*
* sleep 收到中断后会清除中断状态并抛出异常。
* stopping 已经是 true,说明这次中断是停止请求的一部分。
*/
if (!stopping) {
// 如果中断不是本任务预期的停止信号,不要静默吞掉它。
Thread.currentThread().interrupt();
throw new IllegalStateException(
"interrupted without stop request", e);
}
} finally {
System.out.println(Thread.currentThread().getName()
+ " cleanup");
}
System.out.println(Thread.currentThread().getName()
+ " terminated");
}
}
public static void main(String[] args) throws InterruptedException {
Worker worker = new Worker();
Thread thread = Thread.ofPlatform()
.name("worker")
.unstarted(worker);
System.out.println("before start: " + thread.getState());
thread.start();
Thread.sleep(2_200);
System.out.println("observed state: " + thread.getState());
worker.requestStop(thread);
/*
* join 同时完成两件事:
* 1. 等待 worker 结束;
* 2. 建立 worker 结束与当前线程后续读取之间的同步关系。
*/
thread.join();
System.out.println("after join: " + thread.getState());
}
}
一种可能的输出是:
before start: NEW
worker started
working...
working...
working...
observed state: TIMED_WAITING
worker cleanup
worker terminated
after join: TERMINATED
输出中的状态和 working... 次数存在时序差异。例如,主线程打印 observed state 时,工作线程可能刚好从 sleep 返回并重新进入循环,因此状态不一定是 TIMED_WAITING。
3. 停止请求的因果链
这段程序的退出过程可以按以下顺序推导:
- 主线程执行
worker.stopping = true; - 因为
stopping是volatile,工作线程后续读取该字段时能够看到这个写入; - 主线程调用
thread.interrupt(); - 如果工作线程正在
sleep,sleep提前结束并抛出InterruptedException; - 工作线程进入
catch,发现stopping == true; - 工作线程不再进入下一轮工作,执行
finally; run()返回,线程进入TERMINATED;- 主线程的
join()返回; - 主线程继续执行后,可以依赖线程终止相关的同步关系读取工作线程产生的结果。
这里不能只依赖中断状态来表达业务停止条件,因为可中断方法可能清除中断状态。也不能只依赖普通字段,因为普通字段可能存在可见性问题。volatile 状态负责表达业务意图,interrupt() 负责唤醒阻塞点,两者承担不同职责。
4. 中断竞态为什么仍然需要检查停止条件
存在一种竞态:
- 工作线程检查
stopping,结果为false; - 主线程设置
stopping = true并调用interrupt(); - 工作线程开始执行下一步操作,或者刚好进入阻塞方法;
- 工作线程收到中断并退出。
这通常是可以接受的,因为协作退出只能保证线程尽快停止,而不是保证在某个任意指令之间停止。
更危险的是只写:
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
return;
}
如果中断有其他含义,例如上层取消、执行器关闭或资源回收,那么仅凭一次中断就直接退出,可能让当前任务在没有完成必要清理的情况下结束。退出策略应由任务协议决定,而不是由异常名称单独决定。
七、InterruptedException 的正确处理边界
1. 能向上传播时优先传播
如果方法可以声明受检异常,应保留中断语义:
void awaitWork() throws InterruptedException {
queue.take();
}
调用方可以决定是重试、取消任务、恢复状态,还是终止更高层流程。
2. 不能传播时恢复中断状态
Runnable.run() 不能声明受检异常,因此常见处理方式是恢复状态并返回:
@Override
public void run() {
try {
queue.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
错误方式是静默吞掉异常:
catch (InterruptedException e) {
// 什么也不做
}
这会同时丢失异常信息和中断状态,使外层代码误以为任务仍应继续运行。
3. 不要无条件恢复后继续循环
下面的代码也可能有问题:
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
InterruptedException 清除了中断状态,代码又在 catch 中恢复状态,但循环没有退出。下一次 sleep() 可能立即再次因已设置的中断状态抛出异常,形成快速循环,造成 CPU 消耗。
如果中断代表取消,应在恢复状态后退出:
catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
八、线程退出时的清理和异常路径
线程结束有两条主要路径:
正常路径:run() 返回
异常路径:未捕获异常离开 run()
无论哪条路径,只要控制流离开 try,finally 都有机会执行:
Thread t = new Thread(() -> {
try {
// 工作
} finally {
// 释放本线程负责的资源
}
});
但“有机会执行”不是“进程退出时绝对执行”。如果线程是守护线程并且 JVM 正在退出,不能把整个清理协议建立在守护线程的 finally 上。
未捕获异常不会让创建线程的 start() 调用抛出异常:
Thread t = new Thread(() -> {
throw new RuntimeException("worker failed");
});
t.start();
System.out.println("start returned");
start returned 仍可能先输出,异常由工作线程自己的未捕获异常处理机制处理。生产代码通常需要显式记录异常,或通过 Future、任务结果对象、监控组件把失败状态传递给管理线程。
九、常见误解与诊断方法
1. “线程处于 RUNNABLE 就一定在执行”
错误。RUNNABLE 包含等待 CPU 的线程。要判断是否存在 CPU 饥饿、锁竞争或阻塞,应结合线程转储、CPU 使用率、锁信息和业务指标,而不能只看 Thread.State。
2. “调用 interrupt() 就能杀死线程”
错误。中断只改变中断状态,或让特定可中断阻塞方法提前返回。无限循环、忽略中断的代码、不可中断的外部调用,都可能继续运行。
3. “线程状态能用于同步”
错误。状态是观察信息,不是可靠的协调协议。getState() 返回后状态可能立即变化。需要等待线程结束时使用 join();需要等待条件时使用明确的条件变量、队列或同步器。
4. “守护线程会在 JVM 退出前完成清理”
错误。守护属性只影响 JVM 是否继续存活,不提供任务完成保证。需要可靠完成的任务必须由非守护线程或显式生命周期管理机制承载。
5. “捕获 InterruptedException 后继续工作没有问题”
通常错误。这样会丢失取消信号,导致线程无法退出,在线程池中还可能使工作线程继续执行已经被调用方取消的任务。
6. 生产诊断中的状态解释
当线程转储中出现大量:
BLOCKED:重点检查synchronized监视器竞争和锁持有时间;WAITING:检查无限等待、通知丢失、线程是否永远没有退出条件;TIMED_WAITING:区分正常的定时等待、重试退避和异常长时间阻塞;RUNNABLE且 CPU 很高:检查忙等、无限循环和中断未检查;TERMINATED:结合未捕获异常日志确认是正常完成还是异常退出。
这些状态只能缩小排查范围,最终仍要结合代码中的锁、队列、取消协议和资源状态分析因果链。
十、与执行器取消的关系
直接管理 Thread 时,协作退出通常由“停止标志 + interrupt() + join()”组成。
使用执行器时,类似请求通常由:
future.cancel(true);
或:
executor.shutdownNow();
发出。参数 true 或 shutdownNow() 通常意味着尝试中断正在运行的任务,但它们仍然不是强制终止机制:
- 任务必须检查中断或调用可中断阻塞方法;
- 已经完成的任务不会被回滚;
- 正在执行不可中断操作的任务可能继续运行;
- 线程池的关闭状态与任务自身的业务停止条件仍需协调。
因此,从裸线程到执行器,变化的是生命周期管理者,不变的是核心原则:取消必须被任务主动观察并协作执行。
Java 线程从 NEW 到 TERMINATED 的生命周期由 start()、线程体执行、阻塞机制和正常或异常返回共同构成;interrupt() 负责传递取消或唤醒请求;守护属性只决定线程是否阻止 JVM 退出;真正可靠的停止则需要可见的停止条件、正确处理中断,并通过 join() 或更高层协调机制确认线程已经结束。
系列导航与关联阅读
- 系列入口:Java 完整学习路线:从 Java 25 语言与 JVM 到 Spring、微服务和生产交付
- 上一篇:Java Collector 深入:归约、分组、下游收集器、并行和自定义
- 下一篇:Java synchronized 与 Monitor:锁升级、等待通知、可见性和死锁
官方资料
本文依据 Java、Spring 与相关项目官方文档重新梳理;正文、示例与生产清单由 WR BLOG 编写。

评论
0 条讨论