如何在Java中创建线程安全的一次性read-many值?

Law*_*Dol 9 java multithreading

这是我在处理更复杂系统时经常遇到的问题,而且我从来没有想过要解决的好方法.它通常涉及共享对象主题的变体,其构造和初始化必然是两个不同的步骤.这通常是因为体系结构要求,类似于applet,因此建议我合并构造和初始化的答案是没有用的.系统最迟必须以Java 4为目标,因此建议仅在以后的JVM中提供支持的答案也没有用.

举个例子,假设我有一个类,它被构造成适合应用程序框架,如下所示:

public class MyClass
{

private /*ideally-final*/ SomeObject someObject;

MyClass() {
    someObject=null;
    }

public void startup() {
    someObject=new SomeObject(...arguments from environment which are not available until startup is called...);
    }

public void shutdown() {
    someObject=null; // this is not necessary, I am just expressing the intended scope of someObject explicitly
    }
}
Run Code Online (Sandbox Code Playgroud)

我无法使someObject成为final,因为在调用startup()之前无法设置它.但我真的希望它反映它的一次写入语义,并能够从多个线程直接访问它,最好避免同步.

我的想法是表达和执行一定程度的最终性,我猜想我可以创建一个通用的容器,就像这样(UPDATE - 更正此类的线程化代码):

public class WormRef<T>
{
private volatile T                      reference;                              // wrapped reference

public WormRef() {
    reference=null;
    }

public WormRef<T> init(T val) {
    if(reference!=null) { throw new IllegalStateException("The WormRef container is already initialized"); }
    reference=val;
    return this;
    }

public T get() {
    if(reference==null) { throw new IllegalStateException("The WormRef container is not initialized"); }
    return reference;
    }

}
Run Code Online (Sandbox Code Playgroud)

然后在MyClass上面做:

private final WormRef<SomeObject> someObject;

MyClass() {
    someObject=new WormRef<SomeObject>();
    }

public void startup() {
    someObject.init(new SomeObject(...));
    }

public void sometimeLater() {
    someObject.get().doSomething();
    }
Run Code Online (Sandbox Code Playgroud)

这为我提出了一些问题:

  1. 有没有更好的方法,或现有的Java对象(必须在Java 4中可用)?

其次,在线程安全方面:

  1. 这是否是线程安全的,只要在调用someObject.get()之后没有其他线程访问set().其他线程只会在startup()和shutdown()之间调用MyClass上的方法 - 框架保证这一点.
  2. 给定完全不同步的WormReference容器,在任何一个JMM下都可以看到一个值object既不是null也不是对SomeObject的引用?换句话说,JMM是否始终保证没有线程可以观察到对象的内存是分配对象时堆上发生的任何值.我认为答案是肯定的,因为分配显式地将分配的内存归零 - 但CPU缓存是否会导致在给定的内存位置观察到其他内容?
  3. 是否足以使WormRef.reference为volatile以确保正确的多线程语义?

请注意,这个问题的主要目的是如何表达和执行最终someObject而不能实际标记它final; 次要是线程安全所必需的.也就是说,不要过于担心线程安全问题.

Law*_*Dol 0

这是我的最终答案,Regis 1

/**
 * Provides a simple write-one, read-many wrapper for an object reference for those situations
 * where you have an instance variable which you would like to declare as final but can't because
 * the instance initialization extends beyond construction.
 * <p>
 * An example would be <code>java.awt.Applet</code> with its constructor, <code>init()</code> and
 * <code>start()</code> methods.
 * <p>
 * Threading Design : [ ] Single Threaded  [x] Threadsafe  [ ] Immutable  [ ] Isolated
 *
 * @since           Build 2010.0311.1923
 */

public class WormRef<T>
extends Object
{

private volatile T                      reference;                              // wrapped reference

public WormRef() {
    super();

    reference=null;
    }

public WormRef<T> init(T val) {
    // Use synchronization to prevent a race-condition whereby the following interation could happen between three threads
    //
    //  Thread 1        Thread 2        Thread 3
    //  --------------- --------------- ---------------
    //  init-read null
    //                  init-read null
    //  init-write A
    //                                  get A
    //                  init-write B
    //                                  get B
    //
    // whereby Thread 3 sees A on the first get and B on subsequent gets.
    synchronized(this) {
        if(reference!=null) { throw new IllegalStateException("The WormRef container is already initialized"); }
        reference=val;
        }
    return this;
    }

public T get() {
    if(reference==null) { throw new IllegalStateException("The WormRef container is not initialized"); }
    return reference;
    }

} // END PUBLIC CLASS
Run Code Online (Sandbox Code Playgroud)

(1)授予游戏节目《原来你想成为百万富翁》,由Regis Philburn主持。