Java 并发模型:从 JMM 到锁升级的完整路径
2026/7/17 7:57:08 网站建设 项目流程

Java 从诞生之初就将多线程支持内置在语言中——synchronized关键字、Object.wait()/notify()Thread类都是 JDK 1.0 就存在的。但这种“内置”并不意味着简单。并发编程的复杂性来自硬件、操作系统和 JVM 三个层面的叠加,理解每一层如何相互作用,才可能写出正确且高效的多线程代码。


一、Java 内存模型:并发编程的契约

Java 内存模型定义了一套规则,规范了多线程环境下共享变量的访问行为。它不是描述内存的物理布局,而是描述什么情况下一个线程对共享变量的修改对另一个线程可见。

可见性问题来源于现代 CPU 的多级缓存结构。CPU 核读写变量时,数据可能停留在 L1/L2/L3 缓存中,尚未写回主存。另一个核从主存读取同样的变量,看到的是旧值。这不是 Java 的问题,这是计算机体系结构的问题。JMM 通过定义 happens-before 规则来解决可见性。

happens-before 关系是判断两个操作是否有序的依据:如果操作 A happens-before 操作 B,那么 A 的结果对 B 可见。关键规则包括:

  • 程序次序规则:单线程中,按代码顺序执行的操作存在 happens-before 关系

  • volatile 变量规则:对 volatile 变量的写入 happens-before 后续对该变量的读取

  • 锁规则:解锁操作 happens-before 后续对该锁的加锁操作

  • 传递性:A happens-before B,B happens-before C,则 A happens-before C

原子性问题涉及不可分割的操作。i++不是原子操作——它包含读取、修改、写入三个步骤,多个线程同时执行时可能丢失更新。JMM 保证对longdouble之外的读写操作是原子的,但复合操作必须使用同步机制来保证原子性。

有序性问题源于编译器和 CPU 对指令的重排序。只要不改变单线程的执行结果,编译器和 CPU 可以自由重排指令以提高性能。但在多线程环境中,重排序可能导致意料之外的执行顺序。volatilesynchronized都包含内存屏障,防止重排序跨越临界区。

JMM 的实质:不提供绝对的内存一致性,而是提供一个可靠性边界——使用正确的同步机制,程序就能在任意平台上得到预期的行为;不使用,则没有任何保证。


二、volatile:轻量级同步机制

volatile是最轻量的同步机制,它保证可见性和有序性,但不保证原子性。

写入 volatile 变量会强制将值刷新到主存,随后其他线程读取该变量时会从主存获取最新值。编译器不会将 volatile 变量的操作重排序到内存屏障之外。这些特性使 volatile 适合作为状态标记(如volatile boolean running = true;控制循环终止)。

但 volatile 无法保证复合操作的原子性。典型的错误用法是volatile int counter;然后counter++——递增操作依然存在竞态条件。需要原子递增时,应使用AtomicInteger


三、synchronized:从偏向锁到重量锁的演进

synchronized是 Java 最基础的同步原语,但其底层实现在 JDK 演进过程中经历了多次重构。早期的synchronized是重量级锁,每次加锁都会切换到内核态,性能开销极大。从 JDK 1.6 开始引入了锁升级机制:

偏向锁:锁默认偏向第一个获得它的线程。当锁被一个线程反复获取时,偏向锁只需检查 Mark Word 中的线程 ID,无需 CAS 操作。偏向锁通过取消偏向来竞争释放。

轻量级锁:当第二个线程尝试获取偏向锁时,锁升级为轻量级锁。线程通过 CAS 竞争锁,自旋等待。轻量级锁不涉及内核态切换,适合锁竞争不激烈的场景。

重量级锁:自旋超过一定次数后,锁升级为重量级锁。线程被阻塞,等待操作系统调度唤醒。重量级锁涉及用户态到内核态的切换,开销较大。

锁只能升级不能降级,这是为了减少取消偏向的开销。锁升级的阈值可以通过-XX:BiasedLockingStartupDelay-XX:PreBlockSpin调整。

锁的膨胀过程与对象头中的 Mark Word 紧密相关——对象头存储了锁的状态标志和指向锁记录的指针。理解锁升级,需要理解对象头中的状态位在不同场景下如何变化。


四、CAS:无锁并发的基础

CAS 是一条 CPU 原子指令,比较并交换:从内存读取值 V,与预期值 A 比较,相等则将内存值更新为 B,否则返回失败。Java 通过Unsafe类提供compareAndSwapObjectcompareAndSwapIntcompareAndSwapLong等方法暴露 CAS。

AtomicInteger.incrementAndGet()的实现在循环中调用 CAS,直到成功为止。这种自旋 CAS 在高并发场景下可能导致频繁的重试和 CPU 开销。

CAS 的 ABA 问题:内存值从 A 变为 B 再变回 A,CAS 认为没有变化,但实际上发生了变化。AtomicStampedReference通过版本号解决 ABA 问题。实际工程中 ABA 问题的破坏性取决于应用场景,不是所有场景都需要处理。


五、AQS:同步器的基础框架

AbstractQueuedSynchronizerjava.util.concurrent包的核心,ReentrantLockSemaphoreCountDownLatchReentrantReadWriteLock都基于 AQS 实现。

AQS 的核心是一个volatile int state和一个等待队列。子类通过tryAcquiretryRelease等钩子方法控制 state 的获取和释放逻辑。

独占模式(如ReentrantLock):state 为 0 表示锁空闲,成功获取锁时 state 加 1,释放时减 1。重入时 state 多次递增。

共享模式(如Semaphore):state 表示剩余许可数,每次获取减 1,释放加 1。

等待队列是一个 CLH 队列(Craig, Landin, and Hagersten 队列)的变体,通过自旋和LockSupport.park()实现线程阻塞与唤醒。

ReentrantLocksynchronized的选择:synchronized更简洁,锁升级机制让它在大多数场景性能足够;ReentrantLock提供更多功能——可中断锁、超时锁、公平锁、多条件变量。这些功能不是日常所需,但在特定场景下不可替代。


六、并发容器与工具

ConcurrentHashMapjava.util.concurrent包中最重要的容器。在 JDK 1.7 中采用分段锁,将数据分为多个 Segment,每个 Segment 独立加锁。JDK 1.8 改为 CAS + synchronized:插入时先 CAS 尝试,失败再使用 synchronized 锁定目标 Node。这种设计让ConcurrentHashMap的并发度远高于HashtableCollections.synchronizedMap

CopyOnWriteArrayList在写操作时复制整个底层数组。读操作不需要锁,但写操作开销大且数据有延迟。适合读多写少的场景,如配置列表的读取。

BlockingQueue接口的实现(如ArrayBlockingQueueLinkedBlockingQueue)用于生产者消费者模式,提供阻塞的puttake操作。

CountDownLatchCyclicBarrier用于线程协调。前者使一个或多个线程等待,直到一组操作完成;后者让一组线程互相等待,到达共同屏障点后继续执行。


七、实际开发中的陷阱

this引用逸出:在构造函数中启动线程或将this传递给外部,可能导致对象尚未完全初始化就被其他线程访问。修复方法是使用静态工厂方法,在构造完成后才暴露对象引用。

双重检查锁定的经典写法在 JDK 1.5 之前是失效的,因为new操作可能重排序导致其他线程看到未初始化的对象。使用volatile修饰实例变量可以修复此问题,但更好的方式是使用静态内部类单例或Enum实现。

ThreadLocal内存泄漏ThreadLocalMap的 key 使用弱引用,但 value 是强引用。线程池中的线程长时间存活,ThreadLocal 的 value 可能在不再使用后仍然被引用。解决方案是在finally块中调用ThreadLocal.remove()


八、性能调优建议

锁竞争导致的上下文切换成本远高于 CAS 自旋。判断锁竞争程度:用jstack查看线程状态,大量线程处于BLOCKED状态说明锁竞争严重。应对措施包括:缩小临界区范围、使用ReentrantLocktryLock减少阻塞、用ConcurrentHashMap替代同步 Map、用读写锁分离读写的互斥。

volatile的写入操作会触发内存屏障,但读操作没有同步开销。适当地在状态标记中使用 volatile,可以避免不必要的锁开销。


九、小结

Java 的并发模型建立在 JMM、synchronized 锁升级机制和 AQS 框架之上。理解 JMM 的 happens-before 规则是正确性基础,了解synchronized的锁升级路径才能评估锁的性能开销,掌握 AQS 才能灵活使用 JUC 工具类。并发不是独立于其他知识模块的“附加特性”——它贯穿于 JVM 的每一个角落,是 Java 语言区别于其他系统级语言的核心特性之一。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询