Java内存模型

 

JVM如何从入门到放弃的?

之前在写JVM的时候,还一度把JVM内存结构与Java内存模型给搞混了~~~还好有热心的网友给我指出来。

JVM内存结构:

操作变量时的规则:

  • Java内存模型规定了所有的变量都存储在主内存
  • 线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝
  • 线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量

 

工作内存同步回主内存实现是通过以下的8种操作来完成:

  • lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
  • unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  • write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

 

Java内存模型是围绕着在并发过程中如何处理原子性、可见性和有序性这3个特征来建立的

 

保证原子性的操作:

  • read、load、assign、use、store和write
  • synchronized锁

保证有序性(重排序导致无序)的操作:

  • volatile
  • synchronized锁

保证可见性:

  • volatile
  • synchronized锁
  • final

 

在上面也说了,有序性可以通过volatile和synchronized锁来保证,但我们一般写程序的时候不会总是关注代码的有序性的。其实,我们Java内部中有一个原则,叫做先行发生原则(happens-before)

 

  • “先行发生”(happens-before)原则可以通过:几条规则一揽子地解决并发环境下两个操作之间是否可能存在冲突的所有问题
  • 有了这些规则,并且我们的操作是在这些规则定义的范围之内。我们就可以确保,A操作肯定比B操作先发生(不会出现重排序的问题)

 

“先行发生”(happens-before)原则有下面这么几条:

  • 程序次序规则(Program Order Rule):在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。
  • 管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁,而“后面”是指时间上的先后顺序。
  • volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”同样是指时间上的先后顺序。线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。
  • 线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
  • 线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。
  • 对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
  • 传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。

参考资料: