使用锁保护非易失性字段的初始化?

Pav*_*zky 6 java concurrency

出于教育目的,我正在编写一个简单的版本AtomicLong,其内部变量受到保护ReentrantReadWriteLock.这是一个简化的例子:

public class PlainSimpleAtomicLong {

  private long value;

  private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

  public PlainSimpleAtomicLong(long initialValue) {
    this.value = initialValue;
  }

  public long get() {
    long result;

    rwLock.readLock().lock();
    result = value;
    rwLock.readLock().unlock();

    return result;
  }

  // incrementAndGet, decrementAndGet, etc. are guarded by rwLock.writeLock()
}
Run Code Online (Sandbox Code Playgroud)

我的问题:由于"value"是非易失性的,其他线程是否有可能通过不正确的初始值来观察PlainSimpleAtomicLong.get()?例如,线程T1创建L = new PlainSimpleAtomicLong(42)并与线程共享引用T2.为T2保证遵守L.get()为42?

如果没有,包装this.value = initialValue;到写锁定/解锁会有所作为吗?

ass*_*ias 4

第 17 章用发生在关系之前的方式解释并发代码。this.value = initialValue;在您的示例中,如果您采用两个随机线程,则和之间不存在发生之前关系result = value;

所以如果你有类似的东西:

  1. T1.start();
  2. T2.start();
  3. ...
  4. T1:L = new PlainSimpleAtomicLong(42);
  5. T2:long value = L.get();

您拥有的唯一发生前(hb) 关系(除了每个线程中的程序顺序)是:1 & 2 hb 3,4,5。

但4和5没有订购。然而,如果 T1L.get()在 T2 调用之前调用L.get()(从挂钟的角度来看),那么T1 和T2之间就会有hb关系。unlock()lock()

正如已经评论过的,我认为您提出的代码不会破坏 JVM/硬件的任何组合,但它可能会破坏 JMM 的理论实现。

至于您将构造函数包装在锁定/解锁中的建议,我认为这还不够,因为至少在理论上,T1 可以在运行构造函数主体之前释放对 L 的有效引用(非空)。因此,风险在于 T2 可能会在 T1 在构造函数中获取锁之前获取锁。再说一次,这种交错在当前的 JVM/硬件上可能是不可能的。

所以总而言之,如果你想要理论上的线程安全,我认为你不能没有volatile long value,这就是AtomicLong实现方式。volatile将保证在发布对象之前初始化该字段。最后请注意,我在这里提到的问题并不是由于您的对象不安全(请参阅@BrettOkken 答案),而是基于对象未跨线程安全发布的场景。