天天看点

并发编程学习(17)-----Java内存模型引言:一.平台的内存模型二.Happens-Before关系

思维导图:

并发编程学习(17)-----Java内存模型引言:一.平台的内存模型二.Happens-Before关系

引言:

    在前面的文章中,我们刻意的避免了对java内存模型JMM的介绍。实际上,正是java内存模型决定了对java代码的重排序。重排序实际就是多个线程对变量改动的不可见的原因,因为在代码逻辑上A线程成的操作在B线程的操作之前进行,但是进过JVM进行重排序后,可能B线程的操作就会在A线程之前进行。通过了解java内存模型,我们可以知道,满足什么样的条件,JVM就不会进行重排序。

    本章我们会通过原理部分对java内存模型进行介绍。

  • 原理部分:先介绍JVM的java内存模型,然后介绍Happens-Before关系,JVM由这个关系决定是否重排序。

一.平台的内存模型

    在共享内存的多处理器体系架构中,每个处理器都拥有自己的缓存,并且定期的与主内存进行协调。所以,当保持最小的缓存一致性时,系统会允许不同的处理器在同一时刻从同一储存位置看到的值时不同的。如果想要确保在任意时间各个处理器在同一位置看到的值相同则需要相当大的开销,而这在大多数时间是不需要的。

    我们的程序会执行一种简单假设,想象在程序中只存在唯一的操作顺序,而不考虑这些操作在何种处理器上执行,并且在每次读取变量时,都能会得在执行序列中最近一次写入该变量的值。这个乐观的模型被称为串行一致性。

    在实际操作中,如果多个线程访问同一位置时,很有可能出现数据不同步的问题。因为在编译期生成指令顺序时是可以与源代码中的顺序不同。此外,编译期也可能会把变量保存在寄存器中而不是内存中,处理器也可能采用乱序或并行等方式来执行指令,缓存也可能会改变将写入变量提交到主存的次序,而且,保存在处理器本地缓存的值在其他处理器是不可见的。这些都会导致多个线程访问同一个数据但是结果却不同步的问题。所以,我们在编写并发程序时需要借助锁或者非阻塞同步机制来进行控制。

二.Happens-Before关系

    java内存模型为所有的操作定义了一个偏序关系,称之为Happens-Before。如果想要保证执行操作B的线程看到操作A的结果,那么在A和B之间必须满足Happens-Before关系,无论他们是否是在同一个线程之中。

Happens-Before关系如下所示:

  1. 程序顺序规则:如果程序中操作A在操作B之前,那么在线程中A操作将在B操作之前执行。
  2. 监视器锁规则:在监视器锁上的解锁操作必须在同一个监视器锁的加锁操作之前执行。
  3. volatile变量规则:对volatile变量的写入操作必须在对该变量的读取操作之前执行。
  4. 线程启动规则:在线程上对Thread.start的调用不许在该线程中执行任何操作之前执行。
  5. 线程结束规则:线程中的任何操作都必须在其他线程检测到该线程结束之前执行,或者从Thread.join中成功返回,或者在调用Thread.isAlive时返回false
  6. 中断规则:当一个线程在另一个线程中调用intterupt时,必须在中断线程检测到interrupt调用之前执行。
  7. 终结器规则:对象的构造器函数必须在启动该对象的终结器之前执行完成。
  8. 传递性规则:如果操作A在操作B之前执行,并且操作B在操作C之前执行,那么操作A必须在操作C之前执行。

    以上就是Happens-Before规则,当我们需要最大限度的提升某些类的性能时,我们可以利用此规则实现对某个未被锁保护的变量的访问操作进行排序。但是这种操作对执行顺序非常敏感,所以特别容易出错。

继续阅读