【问题标题】:Why don't we need volatile with StampedLock?为什么我们不需要带有 StampedLock 的 volatile ?
【发布时间】:2018-02-07 22:07:58
【问题描述】:

给出来自 Oracle 文档https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/StampedLock.html的代码示例

class Point {
   private double x, y;
   private final StampedLock sl = new StampedLock();

   void move(double deltaX, double deltaY) { // an exclusively locked method
     long stamp = sl.writeLock();
     try {
       x += deltaX;
       y += deltaY;
     } finally {
       sl.unlockWrite(stamp);
     }
   }

   double distanceFromOrigin() { // A read-only method
     long stamp = sl.tryOptimisticRead();
     double currentX = x, currentY = y;
     if (!sl.validate(stamp)) {
        stamp = sl.readLock();
        try {
          currentX = x;
          currentY = y;
        } finally {
           sl.unlockRead(stamp);
        }
     }
     return Math.sqrt(currentX * currentX + currentY * currentY);
   }

   void moveIfAtOrigin(double newX, double newY) { // upgrade
     // Could instead start with optimistic, not read mode
     long stamp = sl.readLock();
     try {
       while (x == 0.0 && y == 0.0) {
         long ws = sl.tryConvertToWriteLock(stamp);
         if (ws != 0L) {
           stamp = ws;
           x = newX;
           y = newY;
           break;
         }
         else {
           sl.unlockRead(stamp);
           stamp = sl.writeLock();
         }
       }
     } finally {
       sl.unlock(stamp);
     }
   }
 }

并且假设类Point的所有方法都可以从不同的线程中调用:

为什么我们不需要将字段 x 和 y 声明为 volatile?

是否保证执行Point#moveIfAtOrigin方法的代码在获取StampedLock#readLock后总能看到x和y字段的最新变化?

当我们调用StampedLock#writeLockStampedLock#readLock 时,是否会建立任何类型的内存屏障?

任何人都可以从文档中引用关于这方面的内容吗?

【问题讨论】:

  • 一般而言获取和释放锁确实会创建内存屏障,因此对锁内完成的变量的任何访问(无论是StampedLock 还是其他)都将确保可见性。
  • docs.oracle.com/javase/8/docs/api/index.html?java/util/… 向下滚动到“内存一致性属性”以获得保证和重要位 - “java.util.concurrent 及其子包中所有类的方法将这些保证扩展到更高级别的同步. 特别是:[...]"
  • @pvg 感谢您引用文档,它应该用于正式证明。
  • @DmitryGorbunov 对此有一些元答案,因为这个问题有很多关于 SO 的变体,都略有不同,涉及不同类的 java.util.concurrent - 全套保证并且对 JMM/JLS 的引用很长,并且它们在每个类的文档中都没有重复,更不用说每个方法,但它们在包(和子包文档)中进行了概述。所以如果你遇到这样的事情,那是第一个(不是完全显而易见的)要检查的地方。

标签: java multithreading java-8 volatile memory-barriers


【解决方案1】:

我不知道为什么文档中没有明确引用 - 可能是因为它是隐含的,但在内部执行 Unsafe.compareAndSwapLong 转换为 LOCK CMPXCHG,在 x86 上有 @987654324 @(我假设这样的事情是在其他平台上完成的);所以确实不需要那些volatile

实际上,x86 上具有lock 的任何指令都将具有完整的内存屏障。

【讨论】:

  • 文档中明确引用。
  • @Eugene 这应该可以解释一下。更新了您的答案以包含来自 pvg 引用的页面的引用。感谢您的帮助!
【解决方案2】:

Lock 接口的 Javadoc 声明如下:

内存同步

如 Java 语言规范(17.4 内存模型)中所述,所有 Lock 实现必须强制执行与内置监视器锁提供的相同内存同步语义:

成功的锁定操作与成功的锁定操作具有相同的内存同步效果。 成功的解锁操作与成功的解锁操作具有相同的内存同步效果。 不成功的锁定和解锁操作,以及重入锁定/解锁操作,不需要任何内存同步效果。

即使StampedLock 没有实现Lock,它也有一个类似asReadLock() 的方法:

返回此 StampedLock 的普通 Lock 视图,其中 Lock.lock() 方法映射到 readLock(),其他方法也类似。

它返回StampedLock 的内部类ReadLockView 的实例,它是Lock 的实际实现。

但是因为它只是一个委托者,这意味着原始方法必须创建内存屏障才能遵守Lock 接口的内存同步强制。

【讨论】:

  • 这不是一个答案,但它是一个正确且易于理解的结论。我不确定为什么有人对你的帖子投了反对票。它肯定会增加价值。赞成它。
猜你喜欢
  • 2019-06-09
  • 2012-11-21
  • 1970-01-01
  • 1970-01-01
  • 2014-06-18
  • 2017-02-26
  • 2011-04-03
  • 2017-07-27
  • 2020-09-21
相关资源
最近更新 更多