volatile语义深入理解

happens-before

JSR-133定义了如下happens-before规则

  1. 程序顺序规则:一个线程中的每个操作,happens-before于该线程中任意后续操作。
  2. 监视器锁规则:对于一个锁的解锁,happens-before于后续对这个锁的加锁操作。
  3. volatile变量规则:对于一个volatile域的写,happens-before于任意后续对这个变量的读。
  4. 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
  5. start()规则:线程A执行操作ThreadB.start()(启动线程B),那么A线程ThreadB.start()操作happens-before于线程B中的任意操作。
  6. join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。

java内存模型JMM来保证happens-before规则,happens-before规则不保证执行顺序,只保证可见性,如果程序(指单线程程序和正确同步后的多线程程序)重排序后的执行结果与指定顺序执行一致,那么就可能发生重排序。

volatile的内存语义

被volatile修饰的变量由以下特性:

  • 普通64位变量的写,不能保证原子性,而volatile变量可以保证。
  • 可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。这一点其实就是happens-before规则的第三条。普通变量没有这种可见性保证。这种可见性主要靠一下两点来保证。
  • 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存中。
  • 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
  • 综合以上两个说法,如果有两个线程,对volatile变量先写后读,那么不仅仅是volatile变量自身,包括写这个volatile变量之前写的所有共享变量都将对另外一个读该共享变量的线程可见。这一点可以综合happens-before规则前三点总结出来。
  • 在前面的结论中,volatile变量使得普通变量也具有可可见性还需要一个保证,也是JMM的实现。那就是JMM会限制编译器重排序和处理器重排序,通过插入内存屏障,限制了普通变量与volatile变量之间的重排序,也限制了volatile变量之间的重排序。下面是通过插入内存屏障实现的保证。
  • 当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。
  • 当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。
  • 当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。
  • 限制了普通变量与volatile变量的重排序规则后,volatile的写-读就具有了锁的释放-获取相同的内存语义。

综合happens-before和volatile两部分,能够看出,volatile的具体实现时保证happens-before的原因。当普通变量的读写与volatile变量的读写放在一起时,所有变量的可见性都有了保证。这也是java并发包中锁的实现方式,加锁时先读volatile变量,解锁时最后写入volatile变量。这两前一个解锁之前的所有操作都happens-before于后面加锁后的所有操作。可以看java.util.concurrent.locks.ReentrantLock.Sync类中的nonfairTryAcquire(int acquires)boolean tryRelease(int releases)两个方法的实现,能够看出volatile内存语义和happens-before规则被完美地呈现。

你可能感兴趣的:(Java)