开发者

How do I create a thread-safe write-once read-many value in Java?

开发者 https://www.devze.com 2022-12-23 03:13 出处:网络
This is a problem I encounter frequently in working with more complex systems and which I have never figured out a good way to solve.It usually involves variations on the theme of a shared object whos

This is a problem I encounter frequently in working with more complex systems and which I have never figured out a good way to solve. It usually involves variations on the theme of a shared object whose construction and initialization are necessarily two distinct steps. This is generally because of architectural requirements, similar to applets, so answers that suggest I consolidate construction and initialization are not useful. The systems have to target Java 4 at the latest, so answers that suggest support available only in later JVMs are not useful either.

By way of example, let's say I have a class that is structured to fit into an application framework like so:

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
    }
}

I can't make someObject final since it can't be set until startup() is invoked. But I would really like it to reflect its write-once semantics and be able to directly access it from multiple threads, preferably avoiding synchronization.

The idea being to express and enforce a degree of finalness, I conjecture that I could create a generic container, like so (UPDATE - corrected threading sematics of this class):

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;
    }

}

and then in MyClass, above, do:

private final WormRef<SomeObject> someObject;

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

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

public void sometimeLater() {
    someObject.get().doSomething();
    }

Which raises some questions for me:

  1. Is there a better way, or existing Java object (would have to be available in Java 4)?

Secondarily, in terms of thread safety:

  1. Is this thread-safe provided that no other thread accesses someObject.get() until after its set() has been called. The other threads will only invoke methods on MyClass between startup() and shutdown() - the framework guarantees this.
  2. Given the completely unsynchronized WormReference container, it is ever possible under either JMM to see a value of object which is neither null nor a reference to a SomeObject? In other words, does the JMM always guarantee that no thread can observe the memory of an object to be whatever values happened to be on the heap when the object was allocated. I believe the answer is "Yes" because allocation explicitly zeroes the allocated memory - but can CPU caching result in something else being observed at a given memory location?
  3. Is it sufficient to make WormRef.reference volatile to ensure proper multithreaded semantics?

Note the primary thrust of this question is h开发者_运维百科ow to express and enforce the finalness of someObject without being able to actually mark it final; secondary is what is necessary for thread-safety. That is, don't get too hung up on the thread-safety aspect of this.


I would start by declaring your someObject volatile.

private volatile SomeObject someObject;

Volatile keyword creates a memory barrier, which means separate threads will always see updated memory when referencing someObject.

In your current implementation some threads may still see someObject as null even after startup has been called.

Actually this volatile technique is used a lot by collections declared in java.util.concurrent package.

And as some other posters suggest here, if all else fails fall back to full synchronization.


I would remove the setter method in WoRmObject, and provide a synchronised init() method which throws an exception if (object != null)


Consider using AtomicReference as a delegate in this object-container you're trying to create. For example:

public class Foo<Bar> {
private final AtomicReference<Bar> myBar = new AtomicReference<Bar>();
 public Bar get() {
  if (myBar.get()==null) myBar.compareAndSet(null,init());
  return myBar.get();
 }

 Bar init() { /* ... */ }
 //...
}

EDITED: That will set once, with some lazy-initialization method. It's not perfect for blocking multiple calls to a (presumably expensive) init(), but it could be worse. You could stick the instantiation of myBar into constructor, and then later add a constructor that allows assignment as well, if provided the correct info.

There's some general discussion of thread-safe, singleton instantiation (which is pretty similar to your problem) at, for example, this site.


In theory it would be sufficient to rewrite startup() as follows:

public synchronized void startup() {
    if (someObject == null) someObject = new SomeObject();
}

By the way, although the WoRmObject is final, threads can still invoke set() multiple times. You'll really need to add some synchronization.

update: I played a bit round it and created an SSCCE, you may find it useful to play a bit around with it :)

package com.stackoverflow.q2428725;

import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Test {

    public static void main(String... args) throws Exception {
        Bean bean = new Bean();
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(4);
        executor.schedule(new StartupTask(bean), 2, TimeUnit.SECONDS);
        executor.schedule(new StartupTask(bean), 2, TimeUnit.SECONDS);
        Future<String> result1 = executor.submit(new GetTask(bean));
        Future<String> result2 = executor.submit(new GetTask(bean));
        System.out.println("Result1: " + result1.get());
        System.out.println("Result2: " + result2.get());
        executor.shutdown();
    }

}

class Bean {

    private String property;
    private CountDownLatch latch = new CountDownLatch(1);

    public synchronized void startup() {
        if (property == null) {
            System.out.println("Setting property.");
            property = "foo";
            latch.countDown();
        } else {
            System.out.println("Property already set!");
        }
    }   

    public String get() {
        try {
            latch.await();
        } catch (InterruptedException e) {
            // handle.
        }
        return property;
    }

}

class StartupTask implements Runnable {

    private Bean bean;

    public StartupTask(Bean bean) {
        this.bean = bean;
    }

    public void run() {
        System.out.println("Starting up bean...");
        bean.startup();
        System.out.println("Bean started!");
    }

}

class GetTask implements Callable<String> {

    private Bean bean;

    public GetTask(Bean bean) {
        this.bean = bean;
    }

    public String call() {
        System.out.println("Getting bean property...");
        String property = bean.get();
        System.out.println("Bean property got!");
        return property;
    }

}

The CountDownLatch will cause all await() calls to block until the countdown reaches zero.


It is most likely thread safe, from your description of the framework. There must have been a memory barrier somewhere between calling myobj.startup() and making myobj available to other threads. That guarantees that the writes in startup() will be visible to other threads. Therefore you don't have to worry about thread safety because the framework does it. There is no free lunch though; everytime another thread obtains access to myobj through the framework, it must involve sync or volatile read.

If you look into the framework and list the code in the path, you'll see sync/volatile in proper places that make your code thread safe. That is, if the framework is correctly implemented.

Let's examine a typical swing example, where a worker threads does some calculation, saves the results in a global variable x, then sends a repaint event. The GUI thread upon receiving the repaint event, reads the results from the global variable x, and repaints accordingly.

Neither the worker thread nor the repaint code does any synchronization or volatile read/write on anything. There must be tens of thousands of implementations like this. Luckily they are all thread safe even though the programmers paid no special attention. Why? Because the event queue is synchronized; we have a nice happends-before chain:

write x - insert event - read event - read x

Therefore write x and read x are properly synchronized, implicitly via event framework.


  1. how about synchronization?
  2. No it is not thread safe. Without synchronization, the new state of your variable might never get communicated to other threads.
  3. Yes, as far as I know references are atomic so you will see either null or the reference. Note that the state of the referenced object is a completely different story


Could you use a ThreadLocal that only allows each thread's value to be set once?


There are a LOT of wrong ways to do lazy instantiation, especially in Java.

In short, the naive approach is to create a private object, a public synchronized init method, and a public unsynchronized get method that performs a null check on your object and calls init if necessary. The intricacies of the problem come in performing the null check in a thread safe way.

This article should be of use: http://en.wikipedia.org/wiki/Double-checked_locking

This specific topic, in Java, is discussed in depth in Doug Lea's 'Concurrent Programming in Java' which is somewhat out of date, and in 'Java Concurrency in Practice' coauthored by Lea and others. In particular, CPJ was published before the release of Java 5, which significantly improved Java's concurrency controls.

I can post more specifics when I get home and have access to said books.


This is my final answer, Regis1 :

/**
 * 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

(1) Confer the game show "So you want to be a millionaire", hosted by Regis Philburn.


Just my little version based on AtomicReference. It's probably not the best, but I believe it to be clean and easy to use:

public static class ImmutableReference<V> {
    private AtomicReference<V> ref = new AtomicReference<V>(null);

    public boolean trySet(V v)
    {
        if(v == null)
            throw new IllegalArgumentException("ImmutableReference cannot hold null values");

        return ref.compareAndSet(null, v);
    }

    public void set(V v)
    {
        if(!trySet(v)) throw new IllegalStateException("Trying to modify an immutable reference");
    }

    public V get()
    {
        V v = ref.get();
        if(v == null)
            throw new IllegalStateException("Not initialized immutable reference.");

        return v;
    }

    public V tryGet()
    {
        return ref.get();
    }
}


First question: Why can't you just make start up a private method, called in the constructor, then it can be final. This would ensure thread safety after the constructor is called, as it is invisible before and only read after the constructor returns. Or re-factor your class structure so that the start-up method can create the MyClass object as part of its constructor. In may ways this particular case seems like a case of poor structure, where you really just want to make it final and immutable.

The easy Approach, if the class is immutable, and is read only after it is created, then wrap it in an Immutable List from guava. You can also make your own immutable wrapper which defensively copies when asked to return the reference, so this prevents a client from changing the reference. If it is immutable internally, then no further synchronization is needed, and unsynchronized reads are permissible. You can set your wrapper to defensively copy on request, so even attempts to write to it fail cleanly (they just don't do anything). You may need a memory barrier, or you may be able to do lazy initialisation, although note that lazy initialisation may require further synchronization, as you may get several unsynchronized read requests while the object is being constructed.

The slightly more involved approach would involve using an enumeration. Since enumerations are guaranteed singleton, then as soon as the enumeration is created it is fixed for ever. You still have to make sure that the object is internally immutable, but it does guarantee its singleton status. Without much effort.


The following class could answer your question. Some thread-safety achieved by using a volatile intermediate variable in conjunction with final value keeper in the provided generic. You may consider further increase of it by using synchronized setter/getter. Hope it helps.

https://stackoverflow.com/a/38290652/6519864

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号