开发者

Is it possible to prevent out-of-order execution by using single volatile

开发者 https://www.devze.com 2023-02-07 13:15 出处:网络
By referring article, it is using a pair of volatile to prevent out-of-order execution. I was wondering, is it possible to prevent it using single volatile?

By referring article, it is using a pair of volatile to prevent out-of-order execution. I was wondering, is it possible to prevent it using single volatile?

void fun_by_thread_1() {
    this.isNuclearFactory = true;
    this.factory = new NuclearFactory();
}

void fun_by_thread_2() {
    Factory _factory = this.factory;
    if (this.isNuclearFactory) {
        // Do not operate nuclear factory!!!
        return;
    }
    // If out-of-order execution happens, _factory might 
    // be NuclearFactory instance.
    _factory.operate();
}

Factory factory = new FoodFactory();
volatile boolean isNuclearFactory = false;

The reason I ask, is because I have a single guard flag (similar to isNuclearFactory flag), to guard开发者_Python百科 against multiple variables (similar to many Factory). I do not wish to mark all the Factory as volatile.

Or, shall I fall into the following solution?

void fun_by_thread_1() {
    writer.lock();
    try {
        this.isNuclearFactory = true;
        this.factory = new NuclearFactory();
    } finally {
        writer.unlock();
    }
}

void fun_by_thread_2() {
    reader.lock();
    try {
        Factory _factory = this.factory;
        if (this.isNuclearFactory) {
            // Do not operate nuclear factory!!!
            return;
        }
    } finally {
        reader.unlock();
    }
    _factory.operate();
}

Factory factory = new FoodFactory();
boolean isNuclearFactory = false;

P/S: I know instanceof. Factory is just an example to demonstrate of out-of-order problem.


Your first solution has the problem that if factory is not volatile, there is no visibility guarantee. That is, if it is changed in one thread, other threads may not see the changed value the same time as they see the changed isNuclearFactory value, or may not even see it at all.

So it is possible that

  1. Thread A calls fun_by_thread_1()
  2. Thread B calls fun_by_thread_2() and it sees that isNuclearFactory == true, however still sees the previous value of factory, which may be null, or may refer to a non-nuclear factory.

Luckily, the fix to this particular problem is easy: reverse the order of assignments in fun_by_thread_1().

void fun_by_thread_1() {
    this.factory = new NuclearFactory();
    this.isNuclearFactory = true;
}

Changing a volatile value is propagated to all other threads; moreover, it also guarantees the visibility of all changes made by the current thread prior to touching the volatile variable.

However, this introduces a new data race problem: now it is possible that

  1. Thread B calls fun_by_thread_2() and gets a reference to factory, which happens to point to a non-nuclear factory
  2. Thread A calls fun_by_thread_1()
  3. Thread B tests isNuclearFactory and sees that it is true (as set by Thread A), so it returns without using _factory although it refers to a non nuclear factory!

And it is also possible that

  1. Thread A calls fun_by_thread_1() and creates a new nuclear factory
  2. Thread B calls fun_by_thread_2() and gets a reference to factory, which happens to point to the new nuclear factory
  3. Thread B tests isNuclearFactory and sees that it is still false, so it operates the nuclear _factory!

So my answer to your title question is No. In fact, even with two volatile variables you can easily have issues. Whenever you have two distinct, but logically related variables exposed to multiple threads, the best solution is to encapsulate them into a single object, thus you can update both values at once, by changing the single (volatile) reference.


I would argue your code does not prevent out of order compiler execution with a single volatile. It does with multiple volatiles because volatiles cannot be re-ordered amongst one another but volatile and normal loads/stores can in certain situations be re-oredered, this is one of them.

Based on the JSR 133 cookbook the operation sequence as followed can result in a compiler re-ordering:

 1st - Volatile-store/monitor-exit
 2nd - Normal-load/normal-store

This sequence can allow for compiler re-ordering so it is possible that the method can look like

void fun_by_thread_1() {
    this.factory = new NuclearFactory();
    this.isNuclearFactory = true;
}

This would have unexpected results as you can imagine if fun_by_thread_1 halts after this.factory and fun_by_thread_2 begins after said halt.

Edit:

To answer your other question. If you wanted to introduce a ReadWriteLock you are better off just using two volatiles. The volatile reads on most processors (x86 for example) would be less costly then acquires either read or write locks.

If just for a knowledge point of view of why the reader/writer lock model would work. The compiler is not allowed to hoist reads or writes outside of any lock (whether intrinsic or j.u.c.Lock). It can however lower the read or write within a synchronized block (called lock coarsening).

That being said, the reads and writes within the ReadWriteLock gauntness sequential consistency for the program with multiple threads. What that tells us is even if the compiler will re-order the writes of your this.factory and this.isNuclearFactory (within the lock methods) other threads will see them as if they were in the original (as if serial) order.

In short it may prevent compiler re-orderings outside of the j.u.c.Lock methods but any re-ordering done in the lock methods will not negatively effect the overall program flow.


I get a pretty solid reference article

http://www.ibm.com/developerworks/library/j-jtp03304/

Under the new memory model, when thread A writes to a volatile variable V, and thread B reads from V, any variable values that were visible to A at the time that V was written are guaranteed now to be visible to B

To thread 1, before volatile isNuclearFactory set to true, factory will always be FoodFactory. The above statement should be applied to thread 2 too, as any variable values that were visible to thread 1 at the time that isNuclearFactory was written are guaranteed now to be visible to B.


Regardless of the attribute visibility issues, I don't think you can make this work without a locking mechanism. In your simple example, even if the out of order problem was solved, thread 1 can be preempted after setting isNuclearFactory to true. In that case, thread 2 will miss the execution of a food factory.

0

精彩评论

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