读完《深入理解 Java 虚拟机》第四版》后,我发现 JDK21 把并发的地基改了
最近系统地把《深入理解 Java 虚拟机》第四版》的并发相关章节重新读了一遍。
必须说,这本书依然是 JVM 领域最扎实的一本。
但是当我带着 JDK21 的视角再看“高效并发”这一部分时,脑子里反复出现一个感觉:
书里讲的都对,但时代变了。
今天我只聊三块内容:
-
线程模型
-
锁与同步
-
并发设计思想
不谈 GC,不谈类加载。
一、线程模型:从 1:1 到 N:M,这是“地基级变化”
书里讲线程的时候,默认前提是:Java 线程 = 操作系统线程
也就是 1:1 模型。
这在 JDK8 时代完全正确:
-
每创建一个 Thread,就创建一个内核线程
-
每个线程默认 1MB 栈空间
-
上下文切换是内核调度
-
阻塞会挂起 OS 线程
所以当时我们学并发时,潜意识里有一句话:
线程是重资源。
这直接影响了整套工程实践:
-
必须用线程池
-
线程数不能太多
-
IO 必须用 NIO
-
必须避免阻塞
但是 JDK21 引入了 虚拟线程(Virtual Thread)。
模型变成:Java 虚拟线程 → 由 JVM 调度 多个虚拟线程 → 复用少量 OS 线程
也就是 N:M。
这意味着什么?
-
线程创建成本极低
-
栈不再固定 1MB
-
阻塞时可以从 carrier 线程卸载
-
可以开几十万线程
这不是“优化”。
这是把并发模型从内核态搬到了用户态。
如果用一句话总结:
JDK8 时代在优化“线程数量”,JDK21 时代开始优化“线程结构”。
二、阻塞不再是罪过:工程哲学被改写
书里大量强调:
-
减少线程上下文切换
-
避免阻塞
-
使用异步非阻塞模型
在 JDK8 时代这是对的。
因为阻塞意味着:
-
占用一个 OS 线程
-
线程池耗尽
-
系统崩溃
于是我们写出了大量这样的代码:
CompletableFuture .thenApply() .thenCompose() .thenAccept()
或者 Reactor:
Mono.flatMap().map().subscribe()
我们写复杂的回调链,不是因为喜欢函数式,而是因为:
线程太贵。
但在 JDK21 时代:
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { ... }
或者:
Executors.newVirtualThreadPerTaskExecutor()
你可以用同步阻塞写法,写出类似异步的吞吐能力。
这件事非常关键。
因为它改变了一个核心问题:
现在的重点不再是“如何避免阻塞”,而是“如何避免共享”。
三、锁的意义变了:从减少竞争到减少共享
书里对 synchronized、CAS、volatile、AQS 的讲解依然非常重要。
-
偏向锁
-
轻量级锁
-
锁膨胀
-
自旋
-
CAS 自旋失败退化
但你要知道一件事:
JDK21 已经移除了偏向锁。
原因很简单:
-
当年的 Web 场景:单线程访问对象多
-
现在的微服务:线程短命、高并发、切换频繁
-
偏向锁反而成为负担
也就是说:
JVM 已经承认当年的锁优化策略不再主流。
更重要的是,在虚拟线程时代:
锁竞争更容易出现。
因为你可能创建 10 万个线程去访问共享对象。
所以问题从:如何优化锁
变成:如何减少共享
这是并发设计思想的转移。
四、Monitor 与 synchronized 依然重要,但语境变了
书里讲 Monitor 结构:
-
ObjectMonitor
-
EntryList
-
WaitSet
-
Owner
这些依然成立。
但虚拟线程带来一个新问题:
pin
当虚拟线程在 synchronized 内阻塞时:
-
它不能卸载
-
会占用 carrier 线程
-
导致线程池资源被锁死
这意味着:
在虚拟线程场景下,synchronized 可能比 ReentrantLock 更“危险”。
因为 AQS 支持 park/unpark。
所以:
书里的 Monitor 机制没有错,但使用语境已经发生变化。
五、JMM 没变,但实践重点变了
Java 内存模型(happens-before、内存屏障、volatile)没有改变。
lock addl $0x0,(%esp) 作为内存屏障的原理依然成立。
CPU 的缓存一致性协议依然是 MESI。
但在 JDK21 时代,真正的热点已经转向:
-
Structured Concurrency
-
ScopedValue
-
Structured cancellation
-
ForkJoinPool 与 carrier 线程调度
也就是说:
内存模型没变,但并发抽象层升级了。
六、结构化并发:这是书里完全没有的世界
《深入理解 Java 虚拟机》第四版成书时,结构化并发还不存在。
而在 JDK21:
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { ... }
你可以:
-
自动取消子任务
-
聚合异常
-
控制任务生命周期
-
保证父子线程结构一致
这在 JDK8 是做不到的。
我们当年写的是:
-
Future + get
-
CountDownLatch
-
自己管理取消
现在是语言级并发结构。
这是一种范式升级。
七、总结:JDK8 vs JDK21 并发的根本区别
| 维度 | JDK8 世界 | JDK21 世界 |
|---|---|---|
| 线程 | 重资源 | 轻资源 |
| 模型 | 1:1 | N:M |
| 重点 | 降低线程数 | 降低共享 |
| 异步 | 必须 | 可选 |
| 阻塞 | 危险 | 可接受 |
| 锁优化 | 偏向锁、自旋 | 减少共享、避免 pin |
| 并发结构 | 手动组合 | 结构化并发 |
一句话总结:
JDK8 是“线程稀缺时代”的工程学
JDK21 是“线程廉价时代”的设计学
八、那这本书还值得读吗?
非常值得。
因为:
-
Monitor 原理没变
-
CAS 原理没变
-
JMM 没变
-
AQS 没变
-
CPU 内存模型没变
变的是:
上层并发抽象。
就像学操作系统一样。
你必须理解底层,
但工程实践一定要跟上时代。
九、我的个人建议(给正在读这本书的人)
如果你现在是 JDK21 开发者,我建议你:
-
把书里的并发章节读透
-
理解 Monitor、JMM、CAS
-
再去学:
-
虚拟线程
-
Structured Concurrency
-
ScopedValue
-
pin 问题
-
Carrier 线程调度
-
这样你会有一种感觉:
原来 JVM 的底层没有变,只是并发抽象升级了。
更多推荐




所有评论(0)