3

JCIP warns us against improperly published objects (see here). If an object is mutable, the JVM might decide to publish the object before its initialization was finished.

So the code

class Holder {public int h = 0;public Holder(int _h) {h = _h;}}
a = new Holder(10);

could become effectively

a = new Holder();
// inlined constructor
a.h = 10;

then when different thread access a.h, assuming it can never be 0, it could fail

// Thread #1 executing
a = new Holder();
// Thread #2 preempts
if (a != null) System.out.println("result: "+(100/a.h)); // oops...

I was trying to demonstrate this problem. But no code I wrote demonstrated it. What I did was a holder

static class Holder {
    int h = 0;
    int dummy = 0;
    public Holder(int h) {
        // if construction takes time, maybe I have better chance creating the bug...
        for (long i=0;i<1000*1000;i++) {
            dummy += i*(-1+(i%2*2));
        }
        this.h = h;
    }
    public int verifyNonZero() {
        return 1/h;
    }
}

Then I ran 1000 threads publishing new Holder() to a static holder variable, and other 1000 threads running holder.verifyNonZero(). See full gist.

It didn't really help with Java 1.6, even with -server.

Community
  • 1
  • 1
Elazar Leibovich
  • 32,750
  • 33
  • 122
  • 169

2 Answers2

3

The fact that it's not guaranteed to be safe doesn't mean that things will necessary go wrong in your environment.

As far as I know, there is no evidence that this behaviour can be demonstrated on modern JVMs. However, there are evidences that some older JVMs were affected:

Also note that out-of-order execution can be caused not only by JVM, but also by CPU. However, memory model of x86 CPUs is quite conservative (Who ordered memory fences on an x86?), so that this behaviour cannot be caused by commonly used CPUs as well.

So, I think that it's unlikely that you would be able to demostrate this problem in a typical modern environment.

axtavt
  • 239,438
  • 41
  • 511
  • 482
  • I think JCIP implies this is a problem that can occur in practice. Is there a common JVM where it can be demonstrated? Maybe Android, or OpenJDK? – Elazar Leibovich Dec 20 '11 at 19:10
  • 1
    @Elazar: It's a "write once, run everywhere" principle - they cannot say "it's safe in this particular environment", because you program can be run in different environements, and they are allowed to reorder operations. However, since there are no evidences that this problem exists in modern environments, perhaps there are no ways to demonstrate it. – axtavt Dec 21 '11 at 09:00
0

How are you running your test? [EDIT: oh, you linked to the code!]

The JMM guarantees that within a given thread, you won't be able to see things out of order -- that is, if the same thread is instantiating Holder and checking it, it will never see an un-initialized state.

So, you have to instantiate a Holder in one thread and then see it in another. How do you share that instance? The common methods (a volatile static, a synchronized or thread-safe collection, etc) establish a happens-before relationship between when the Holder was published by its instantiating thread, and when it was read by the other thread.

If you want unsafe publication, your main options are:

  • putting it into a shared but non-thread-safe collection. This is a bit risky for your test, since the collection itself may throw exceptions
  • putting into a non-volatile static
  • putting it into an array (and not using AtomicReferenceArray)

Even then, you're relying on timings to go juuust wrong. I haven't had much luck in reproducing these kinds of races using just a static, as you're doing; I think the code isn't complex enough for the CPUs to do fancy caching.

IBM has an experimental javaagent, ConTest, which purposely tries to exploit concurrency bugs. I gave it a shot a while back on a purposely-thread-dangerous queue (I believe it was an intentional deadlock). Without ConTest, the queue failed fairly rarely; with it, the failures cropped up about 30% of the time. I'm not sure if ConTest is better or worse with memory visibility races.

yshavit
  • 42,327
  • 7
  • 87
  • 124