2

I have read through different articles such as Double-checked locking: Clever, but broken and I understand the reason of why the following code is broken in multi-threading usage.

class SomeClass {
  private Resource resource = null;
  public Resource getResource() {
    if (resource == null) {
      synchronized {
        if (resource == null) 
          resource = new Resource();
      }
    }
    return resource;
  }
}

However, according to its explanation, when a thread exits a synchronized block, it performs a write barrier -- it must flush out any variables modified in that block to main memory before releasing the lock. Therefore, when Thread A runs into the synchronized block, and then does the following process in order:

  1. Memory for the new Resource object will be allocated;
  2. the constructor for Resource will be called,
  3. initializing the member fields of the new object;
  4. the field resource of SomeClass will be assigned a reference to the newly created object

Finally, before Thread A exits from the synchronized block, it will write its local resource object back to the main memory and then Thread B will read this newly created resource from the main memory once it runs into the synchronized block.

Why Thread B may see these memory operations in a different order than the one Thread A executes? I thought Thread B won't know the resource object is created until Thread A flushes its local memory out to main memory when it exits from the synchronized block because Thread B can only read the resource object from the sharable main memory?

Please correct my understanding....Thank you.

Valentin Rocher
  • 11,667
  • 45
  • 59
enix0907
  • 79
  • 1
  • 2
  • 9
  • Sorry for referring this old (2001) article to confuse myself. In Java +5.0 version, how it fixes this issue? Is that because Volatile keyword can guarantee that Thread B won't never read a partially initialized resource object? In other words, Thread B can only read resource object which either 'null' or fully initialized resource object? – enix0907 Aug 23 '12 at 14:23

4 Answers4

2

The article you quoted refers to the Java memory model prior to Java 5.0.

In Java 5.0+, your resource must be declared volatile for that to work. Even if the changes are flushed to the main memory, there's no guarantee (other than volatile) that thread B will read the new value from the main memory rather than its own local cache (where the value is null).

In previous versions, volatile did not impose strict restrictions on reordering so double checked locking was not guaranteed to work properly.

Costi Ciudatu
  • 37,042
  • 7
  • 56
  • 92
1

Is "Double-checked locking" one of the memes which won't die. IMHO using an enum is much smarter (As suggested by Josh Bloch in Effective Java 2nd edition)

enum SomeClass {
    INSTANCE; // thread safe and lazy loaded
}

The bug you are referring to was fixed in Java 5.0, in 2004.

In short, a) don't use it b) use a version of Java 5.0+ c) don't use really old unsupported versions of Java and take really, really old articles (2001) with a grain of salt.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • See here: http://stackoverflow.com/questions/11925539/singleton-with-enum-vs-singleton-with-double-checked-locking – JohnB Aug 23 '12 at 07:42
1

Finally, before Thread A exits from the synchronized block, it will write its local resource object back to the main memory and then Thread B will read this newly created resource from the main memory once it runs into the synchronized block.

This is where it breaks down. Because Thread B accesses resource without synchronizing there is no read barrier on its operations. It may therefore see a stale cached copy of memory for the resource cell or (a bit later) for the cell corresponding to some field of the Resource instance.

Costi Ciudatu's fix is correct for Java versions >= 5.0. But for versions older than that the semantics of volatile did not guarantee that all changes would be flushed through from A to main memory to B.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • Thank for your explanation which makes more sense to me. When you said "Because Thread B accesses resource without synchronizing there is no read barrier on its operations.", does it mean the first "if (resource == null)" above the synchronized block? The other one is inside the synchronized block so I assume it's ok. – enix0907 Aug 23 '12 at 14:16
  • Basically, that is correct. Until the `synchronized` block has executed, there is no happens-before relationship between the action(s) in thread A and the action(s) in thread B. Changing the declaration to `volatile` is one way to add the required happens-before relationship. – Stephen C Aug 23 '12 at 22:36
  • When Thread B accesses the resource in the first "if (resource == null)" above the synchronized block, does it read the resource from its own local memory or from the shared main memory? Before Thread A exits from the synchronized block, the initializing resource object should still in Thread A local memory and hasn't written back to the main memory. If this is true, how can Thread B see a stale cached copy of memory for the resource cell?? I thought Thread B can only read its own local memory until read barrier. – enix0907 Aug 24 '12 at 03:52
  • "I thought Thread B can only read its own local memory until read barrier." That's not true. It could see *either* the contents of main memory, *or* a previously cached copy of that memory location. The read barrier causes the cached copy to be invalidated, causing the CPU to *have to* do a read from main memory. – Stephen C Aug 24 '12 at 06:18
  • When Tread A is initializing the resource object inside the synchronized block, is this object initialized in Thread A's local memory? Then, when it exits from the block, it will write its local copy back to the main memory. If this is true, meanwhile when Thread B reads either from the main memory or its own local memory, it shouldn't know the effect caused by Thread A until Thread A exits from the block. How can Thread B still see the variation of the initialization of the resource object? – enix0907 Aug 24 '12 at 15:16
  • I guess it's because "resource = new Resource()" can be divided into several instructions including its constructor method which are out of this synchronized block...... Thanks for your advanced explanation. – enix0907 Aug 24 '12 at 15:17
  • Your theory (comment before last) is not correct. The Java Memory Model does not constrain the order / pattern of writes like that. Rather than *guessing* how threads behave, you should either read the JLS, or get hold of a copy of "Java Concurrency in Practice" by Brian Goetz, and read the chapter on the memory model. – Stephen C Aug 25 '12 at 08:31
  • Thanks for the reference... very helpful :) So the ordering of instruction execution can be guaranteed inside the synchronized block? – enix0907 Aug 25 '12 at 19:53
  • @enix0907 - only with respect to the thread that is executing them. Please find / read a copy of Goetz. This is too complicated to explain in comments. – Stephen C Aug 26 '12 at 02:18
1

I am not going to say more then the others already did, but because this is such an often used pattern, why not just make an utility method for it? Like:Suppliers Memoize

Eugene
  • 117,005
  • 15
  • 201
  • 306