Java 并发基础之内存模型

Version:1.0 Starthtml:000000206 EndHTML:000175716 StartFragment:000004583 EndFragment:000175611 StartSelection:000004679 EndSelection:000175592 SourceURL:https://zhuanlan.zhihu.com/p/210471606/edit
现如今 , 服务器性能日益增长 , 并发(concurrency)编程已经“深入人心” , 但由于冯诺依式计算机“指令存储 , 顺序执行”的特性 , 使得编写跨越时间维度的并发程序异常困难 , 所以现代编程语言都对并发编程提供了一定程度的支持 , 像 Golang 里面的 Goroutines、Clojure 里面的 STM(Software Transactional Memory)、Erlang 里面的 Actor 。
JAVA 对于并发编程的解决方案是多线程(Multi-threaded programming) , 而且 Java 中的线程 与 native 线程一一对应 , 多线程也是早期操作系统支持并发的方案之一(其他方案:多进程、IO多路复用) 。
本文着重介绍 Java 中线程同步的原理、实现机制 , 更侧重操作系统层面 , 部分原理参考 openjdk 源码 。阅读本文需要对 CyclicBarrier、CountDownLatch 有基本的使用经验 。
JUC在 Java 1.5 版本中 , 引入 JUC 并发编程辅助包 , 很大程度上降低了并发编程的门槛 , JUC 里面主要包括:

  • 线程调度的 Executors
  • 缓冲任务的 Queues
  • 超时相关的 TimeUnit
  • 并发集合(如 ConcurrentHashMap)
  • 线程同步类(Synchronizers , 如 CountDownLatch )
个人认为其中最重要也是最核心的是线程同步这一块 , 因为并发编程的难点就在于如何保证「共享区域(专业术语:临界区 , Critical Section)的访问时序问题」 。
AbstractQueuedSynchronizerJUC 提供的同步类主要有如下几种:
  • Semaphore is a classic concurrency tool.
  • CountDownLatch is a very simple yet very common utility for blocking until a given number of signals, events, or conditions hold.
  • A CyclicBarrier is a resettable multiway synchronization point useful in some styles of parallel programming.
  • A Phaser provides a more flexible form of barrier that may be used to control phased computation among multiple threads.
  • An Exchanger allows two threads to exchange objects at a rendezvous(约会) point, and is useful in several pipeline designs.
通过阅读其源码可以发现 , 其实现都基于 AbstractQueuedSynchronizer 这个抽象类(一般简写 AQS) , 正如其 javadoc 开头所说:
Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues. This class is designed to be a useful basis for most kinds of synchronizers that rely on a single atomic int value to represent state.
也就是说 , AQS 通过维护内部的 FIFO 队列和具备原子更新的整型 state 这两个属性来实现各种锁机制 , 包括:是否公平 , 是否可重入 , 是否共享 , 是否可中断(interrupt) , 并在这基础上 , 提供了更方便实用的同步类 , 也就是一开始提及的 Latch、Barrier 等 。
这里暂时不去介绍 AQS 实现细节与如何基于 AQS 实现各种同步类(挖个坑) , 感兴趣的可以移步美团的一篇文章《不可不说的Java“锁”事》 第六部分“独享锁 VS 共享锁” 。
在学习 Java 线程同步这一块时 , 对我来说困扰最大的是「线程唤醒」 , 试想一个已经 wait/sleep/block 的线程 , 是如何响应 interrupt 的呢?当调用 Object.wait() 或 lock.lock() 时 , JVM 究竟做了什么事情能够在调用 Object.notify 或 lock.unlock 时重新激活相应线程?
带着上面的问题 , 我们从源码中寻找答案 。
Java 如何实现堵塞、通知wait/notifypublic final native void wait(long timeout) throws InterruptedException;public final native void notify();在 JDK 源码中 , 上述两个方法均用 native 实现(即 cpp 代码) , 追踪相关代码
// java.base/share/native/libjava/Object.cstatic JNINativeMethod methods[] = {{"hashCode","()I",(void *)&JVM_IHashCode},{"wait","(J)V",(void *)&JVM_MonitorWait},{"notify","()V",(void *)&JVM_MonitorNotify},{"notifyAll","()V",(void *)&JVM_MonitorNotifyAll},{"clone","()Ljava/lang/Object;",(void *)&JVM_Clone},};通过上面的 cpp 代码 , 我们大概能猜出 JVM 是使用 monitor 来实现的 wait/notify 机制 , 至于这里的 monitor 是何种机制 , 这里暂时跳过 , 接着看 lock 相关实现


推荐阅读