JMM 内存模型

1 JVM内存模型

1.1 缺少同步机制时的性能优化

当代处理器提高性能的手段:

  1. 指令重排措施
  2. 提高时钟频率
  3. 太高并行性
  4. 完备的缓存机制
1.1.1 编译器
  1. 编译器生成的指令顺序可能和源代码的顺序不一致。
  2. 编译器将变量保存在寄存器而不是内存
1.1.2 处理器
  1. 处理器可以使用乱序的方式执行指令
  2. 缓存可以改变将写入变量提交到内存的次序
  3. 保存到缓存中的值对其他处理器是不可见的

1.2 同步机制(JMM)

在多线程环境中,维护程序的串行性将导致很大的性能开销。

1.2.1 JMM

JMM规定了JVM必须遵循的一组最小的保证,这组保证规定了对变量的写入操作在何时将对其他线程可见。

1.2.2 实现

JVM通过在适当的位置上插入内存栅栏来屏蔽在JMM与底层平台内存模型之间的差异。

1.2.3 重排序

内存级的重排序会使程序的行为变得不可预测。
同步将限制编译器、运行时和硬件对内存操作重排序的方式,从而在实施重排序时不会破坏JMM提供的可见性保证

1.2.4 Happens-Before

要想保证执行操作B的线程看到操作A的结果,那么在A和B之间必须满足Happens-Before关系。

1.2.4.1 程序顺序规则

如果程序中操作A在操作B之前,那么线程中A操作将在B操作之前执行。

1.2.4.2 监视器锁规则

在监视器锁上的解锁操作必须在同一个监视器锁上的加锁操作之前执行。

1.2.4.3 volatile规则

对volatile变量的写入操作必须在对该变量的读操作之前执行。

1.2.4.4 线程启动规则

在线程上的start调用必须在该线程中执行任何操作之前执行。

1.2.4.5 线程结束规则

线程中的任何操作都必须在其他线程检测到该线程已经结束之前执行,或者从Thread.join中成功返回,或者在调用Thread.isAlive时返回false

1.2.4.6 中断规则

当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt调用之前执行(抛出InterruptedException,调用isInterrupted或interrupt)。

1.2.4.7 终结器规则

对象的构造函数必须在启动该对象的终结器之前执行完成。

1.2.4.8 传递规则

如果操作A在操作B之前执行,并且操作B在操作C之前完成,那么操作A必须在操作C之前完成。

1.2.5 发布
1.2.5.1 不安全的发布

在初始化一个新的对象时,需要写入多个变量,即新对象中的各个域,同时,在发布一个引用时也需要写入一个变量,即新对象的引用。

如果无法确保发布共享引用的操作在另一个线程加载该共享引用之前执行,那么对新对象的引用的写入操作将与对象中各个域的写入操作重排序,另一个线程可能看不到对象引用的最新值,但同时也将看到对象的某些或全部状态中包含的是无效值,即一个被部分构造对象。

除了不可变对象以外,使用另一个线程初始化的对象通常都是不安全的,除非对象的发布操作是在使用该对象的线程开始使用之前执行。

1.2.5.2 安全的发布

BlockingQueue

volatile

1.2.5.3 安全初始化模型
  1. synchronized方法(使用同步机制保证发布)
  2. 提前初始化(由Classloader负责对象初始化和发布)
  3. ResourceHolder(由ClassLoader负责对象初始化和发布,但用lazy方式运行)
1.2.6 初始化的安全性

初始化安全性将确保,对于被正确构造的对象,所有的线程都能看到由构造函数为对象给各个final域设置的正确值,而不管采用何种方式来发布对象。

对于含有final域的对象,初始化安全性可以防止对对象的初始化引用被重排序到构造函数过程之前。
初始化安全性只能保证通过final域可达的值从构造过程完成时可以的可见性。

wenxinzizhu wechat
扫一扫,添加我的微信,一起交流共同成长(备注为技术学习)