深入理解Java虚拟机-JAVA内存模型与线程

Java内存模型(JMM)

JMM 的核心概念

主内存与工作内存

  • 主内存(Main Memory)是所有线程共享的内存区域,存放着所有变量的值
  • 每个线程都有自己的 工作内存(Working Memory),它是该线程的私有内存区域。线程操作共享变量时,先从主内存将变量拷贝到工作内存中,然后对工作内存中的变量进行修改,最后再将修改结果写回主内存

共享变量

  • JMM共享变量是多个线程可以访问的变量。通常是 static 变量或者实例变量。局部变量是线程私有的,不受 JMM 的影响

内存屏障(Memory Barriers)

  • 内存屏障是指 CPU 或者编译器用来保证操作顺序的一种机制。它通过禁止指令重排,确保某些操作在执行时的顺序

变量的可见性、原子性与有序性

  • 可见性:当一个线程修改了共享变量的值,其他线程能够看到这个修改
  • 原子性:对共享变量的操作要么完全成功,要么完全失败,不会中断。对于一些基本的操作(如 i++)来说,JMM 并不保证其原子性,需要通过同步手段来确保
  • 有序性:JMM 保证每个线程内的代码执行顺序,但不一定保证所有线程之间的执行顺序。为了确保线程之间操作的顺序,JMM 提供了同步机制来控制

JMM 中的关键规则

线程间的可见性保证

  • 可见性问题的核心是,当一个线程修改了共享变量,其他线程如何及时看到这个修改。JMM 的设计通过内存同步(比如锁机制、volatile 关键字、synchronized 关键字等)来确保可见性
  • volatile 关键字:声明为 volatile 的变量会直接从主内存中读取,而不是从线程的工作内存中读取。写入 volatile 变量时,JMM 会保证该写操作对其他线程可见。volatile 确保了可见性,但不能保证原子性有序性

原子性保障

  • 在 JMM 中,只有一些基本的操作(如读取和写入一个 longdouble 类型的变量)是原子的。对于复合操作(如 i++),如果不加同步,可能会出现原子性问题
  • 原子性问题的解决方法:使用 synchronizedReentrantLock 等同步机制来确保原子性

有序性保障

  • JMM 规定了每个线程内的指令执行顺序,但在不同线程之间,JMM 不保证执行顺序。为了控制执行顺序,可以使用 synchronizedvolatileLock 等手段
  • synchronized 关键字synchronized 用于确保代码块的互斥执行,并在释放锁时会刷新工作内存中的值到主内存,从而保证线程间的可见性和顺序性
  • volatile 关键字:保证了变量的写操作立即刷新到主内存,且对该变量的读操作总是直接从主内存读取,避免了线程之间的数据不一致性

JMM 的实现和底层原理

  • MESI 协议(Modified, Exclusive, Shared, Invalid),用于多核 CPU 之间缓存数据的一致性
  • 内存屏障(Memory Barrier) :用于禁止指令重排,确保特定操作的顺序执行

volatile语义

可见性(Visibility)

  • 保证:当一个线程修改了 volatile 变量的值,新值会立即被刷新到主内存;其他线程在读取该变量时,会从主内存中重新加载最新值
  • 实现机制:通过插入 Memory Barrier(内存屏障)或缓存一致性协议(如 MESI 协议)强制同步主内存和工作内存的数据

原子性(Atomicity)

  • 单变量操作:对 volatile 变量的读写操作是原子性的(例如 count++ 不会被拆分为 read+increment+write
  • 复合操作volatile 不能保证复合操作的原子性(例如 i++a = b + c),仍需借助 synchronizedAtomicInteger 等类

禁止指令重排序(Ordering)

  • 编译器优化:编译器和处理器可能会对指令进行重排序以提高性能
  • 读操作:在读取 volatile 变量前插入 Load Barrier,禁止之前的读/写操作被重排到其后
  • 写操作:在写入 volatile 变量后插入 Store Barrier,禁止之后的读/写操作被重排到其前
  • 效果:保证 volatile 变量的读写顺序符合程序逻辑

happens-before规则

程序顺序规则

  • 同一线程内,代码执行顺序与书写顺序一致(编译器和处理器可能重排指令,但需保证单线程结果不变)

监视器锁规则

  • Lock → Unlock:对同一锁的 synchronized 块,Lock 操作必在 Unlock 前发生
  • Unlock → LockUnlock 后,其他线程的 Lock 操作才能获取该锁

volatile 变量规则

  • 写 → 读:对 volatile 变量的写操作,必在后续读操作之前完成
  • 读 → 写:对 volatile 变量的读操作,必在后续写操作之前完成

线程启动规则

  • Thread.start() 必须在新建线程的任何操作之前发生

线程终止规则

  • 线程的 run() 方法结束(正常或异常退出)必在 join() 返回之前发生

中断规则

  • 对线程的 interrupt() 调用必在该线程检测到中断状态(如 isInterrupted())之前发生

对象终结规则

  • 对象的 finalize() 方法执行完必在字段被垃圾回收之前发生(注:finalize() 已废弃)

传递性规则

  • 若 A → B 且 B → C,则 A → C(可通过多条规则推导复杂顺序约束)
全部评论

相关推荐

评论
2
5
分享

创作者周榜

更多
牛客网
牛客企业服务