Java 内存模型

Java 内存模型

可见性 & 原子性 & 有序性

可见性保证:指令不会受 cpu 缓存的影响
可见性指:当一个线程修改了某一个共享变量的值,其他的线程是否能够立即知道这个修改。


原子性保证:指令不会受到线程上下文切换的影响
原子性指:一个操作是不可中断的。
即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
使用关键字 synchronized 来保证:代码块内的操作是原子的


有序性保证指令不会受 cpu 指令并行优化的影响
JVM 会在不影响正确性的前提下,可以调整语句的执行顺序。这种特性称之为『指令重排』,多线程下『指令重排』会影响正确性。

volatile

易变关键字。
它用来修饰成员变量和静态成员变量(放在主存中的变量)。
它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,保证了可见性。
volatile 修饰的变量,可以禁用指令重排。 禁止的是加 volatile 关键字变量之前的代码被重排序。

内存屏障

volatile的底层实现原理是 内存屏障,Memory Barrier (Memory Fence)

  • 对 volatile 变量的写指令后会加入写屏障
  • 对 volatile 变量的读指令前会加入读屏障

可见性

  • 写屏障 (sfence) 保证在该屏障之前的,对共享变量的改动,都同步到主存中
  • 读屏障 (lfence) 保证在该屏障之后的,对共享变量的读取,加载的是主存中的数据

有序性

  • 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
  • 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前

但是不能解决指令交错问题

  • 写屏障仅仅是保证之后的读能够读到新的结果,但不能保证读跑到它前面去
  • 而有序性也只是保证了本线程内相关代码不被重排序

    happens - before 规则

    happens - before 规定了对共享变量的写操作对其它线程的读操作可见,它是可见性与有序性的一套规则总结,抛开以下 happens - before 规则,JMM 并不能保证一个线程对共享变量的写,对于其它线程对该共享变量的读可见

    变量都是指成员变量或静态成员变量

线程解锁 m 之前对变量的写,对于接下来对 m 加锁的其它线程对该变量的读可见

  1. static int x;
  2. static Object m = new Object();
  3. new Thread(() -> {
  4. synchronized (m) {
  5. x = 10;
  6. }
  7. }, "t1").start();
  8. new Thread(() -> {
  9. synchronized (m) {
  10. System.out.println(x);
  11. }
  12. }, "t2").start();

线程对 volatile 变量的写,对接下来其它线程对该变量的读可见

  1. volatile static int x;
  2. new Thread(()->{
  3. x = 10;
  4. },"t1").start();
  5. new Thread(()->{
  6. System.out.println(x);
  7. },"t2").start();

线程 start() 前对变量的写,对该线程开始后对该变量的读可见

  1. static int x;
  2. x = 10;
  3. new Thread(()->{
  4. System.out.println(x);
  5. },"t2").start();

线程结束前对变量的写,对其它线程得知它结束后的读可见(比如其它线程调用 t1.isAlive() 或 t1.join()等待它结束)

  1. static int x;
  2. Thread t1 = new Thread(()->{
  3. x = 10;
  4. },"t1").start();
  5. t1.join();
  6. System.out.println(x);

线程 t1 打断 t2 (interrupt) 前对变量的写,对于其他线程得知 t2 被打断后对变量的读可见(通过 t2.interrupted 或 t2.isInterrupted)

  1. static int x;
  2. public static void main(String[] args) {
  3. Thread t2 = new Thread(()->{
  4. while(true) {
  5. if(Thread.currentThread().isInterrupted()) {
  6. log.debug("{}",x);
  7. break;
  8. }
  9. }
  10. },"t2").start();
  11. new Thread(()->{
  12. try {
  13. Thread.sleep(1000);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. x = 10;
  18. t2.interrupt();
  19. },"t1").start();
  20. while(!t2.isInterrupted()) {
  21. Thread.yield();
  22. }
  23. log.debug("{}",x);
  24. }

对变量默认值(0,false,null)的写,对其它线程对该变量的读可见
具有传递性,如果 x hb-> y 并且 y hb-> z 那么有 x hb-> z ,配合 volatile 的防指令重排,有下面的例子

  1. volatile static int x;
  2. static int y;
  3. public static void main(String[] args) {
  4. new Thread(() -> {
  5. y = 10;
  6. x = 20;
  7. }, "t1").start();
  8. new Thread(() -> {
  9. // x=20 对 t2 可见, 同时 y=10 也对 t2 可见
  10. System.out.println(x);
  11. }, "t2").start();
  12. }