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

出于教育目的,我正在编写一个简单版本的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() } 

我的问题 :由于“value”是非易失性的,其他线程是否可以通过PlainSimpleAtomicLong.get()观察到错误的初始值? 例如,线程T1创建L = new PlainSimpleAtomicLong(42)并与线程T2共享引用。 T2是否保证将L.get()视为42?

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

第17章关于并发代码的原因发生在关系之前。 在你的例子中,如果你采用两个随机线程,那么this.value = initialValue;之间没有发生之前的关系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。 然而,如果T1在T2之前调用L.get()调用L.get() (从挂钟的角度来看)那么你将在T1中的unlock()和T2中的lock()之间建立一个hb关系。

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

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

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

假设您不允许对实例的引用转义构造函数(您的示例看起来很好),那么第二个线程永远不会看到具有任何“value”值的对象,而不是它的构造,因为所有访问都受到保护通过监视器(读写锁)在构造函数中是最终的。

https://www.ibm.com/developerworks/library/j-jtp0618/ http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/Lock.html

我认为对于初始值,两个线程都会看到相同的值(因为它们只有在构造函数完成后才能拥有对象)。

如果更改1个线程中的值,则如果不使用volatile,则其他线程可能看不到相同的值

如果你想实现set,带锁/解锁的包装将无法解决问题 – 这在需要primefaces操作(如增量)时很好。 一世

这并不意味着它会以您想要的方式工作,因为您不控制上下文切换。 例如,如果2个线程调用set,值为4和8,因为您不知道上下文切换何时发生,您不知道谁将首先获得锁定。