Double-checked Locking (DCL) and how to fix it

转自:

http://www.javamex.com/tutorials/double_checked_locking.shtml

http://www.javamex.com/tutorials/double_checked_locking_fixing.shtml           


double-checked locking

The term double-checked locking (and sometimes the initials DCL)is used to refer to an infamous programming "pattern" which attempts to avoidsynchronization when reading the reference to a singeton instance that is constructedthroughlazy initialisation (that is, it is not constructed until it isfirst required). An implemenation using synchronization would be:

public class MyFactory {
  private static MyFactory instance;

  public synchronized static MyFactory getFactory() {
    if (instance == null)
      instance = new MyFactory();
    return instance;
  }
}

The above pattern is perfectly correct. However, some programmers have beenreluctant to use it because on all reads it makes a synchronized access and"synchronization is slow". Spurred by this general fear of "slow synchronization",a popular idiom was to attempt to avoid the synchronization onthe read as follows:

public class MyBrokenFactory {
  private static MyFactory instance;
  private int field1, field2 ...

  public static MyBrokenFactory getFactory() {
    // This is incorrect: don't do it at home, kids!
    if (instance == null) {
      synchronized (MyBrokenFactory.class) {
        if (instance == null)
          instance = new MyFactory();
      }
    }
    return instance;
  }

  private MyBrokenFactory() {
    field1 = ...
    field2 = ...
  }
}

On modern JVMs, this optimisation– even if it were correct–is probably no longer that useful.Unfortunately, it's alsoincorrect. We can show it's incorrect byconsidering what might happen by two threads that are concurrently callingMyBrokenFactory.getFactory():

Thread 1: 'gets in first' and startscreating instance.

Thread 2: gets in just as Thread 1 has writtenthe object reference to memory, but beforeit has written all thefields.

1. Is instance null? Yes.
2. Synchronize on class.
3. Memory is allocated for instance.
4. Pointer to memory saved into instance.





7. Values for field1 and field2 are written
to memory allocated for object.
5. Is instance null? No.
6. instance is non-null, but field1 and
field2 haven't yet been set! 
This thread sees invalid values
for field1 and field2!
How to fix double-checked locking?

So how do we fix the double-checked locking "antipattern"?On the next page, we look at options forfixing double-checked locking.


fixing double-checked locking

On the previous page, we looked at why double-checked lockingis a problem. Now we look at correct idioms that can be used instead.

1. Just use synchronization, stoopid...

It sounds a bit glib, but one option is to just go ahead and use thesynchronization that double-checked locking was trying to avoid. As Goetzet alpoint out, double-checked locking is "an idiom whose utility has largely passed". In otherwords, the notion that we must "avoid synchronization because it's slow" just isn't thecase any more foruncontended synchronization on modern JVMs. So in effect,we could 'revert back' to the synchronized method that we were trying to avoid:

public class MyFactory {
  private static MyFactory instance;

  public static synchronized MyFactory getInstance() {
    if (instance == null)
      instance = new MyFactory();
    return instance;
  }

  private MyFactory() {}
}

2. Use the class loader

One of the most optimised and sophisticated pieces of synchronization in the JVM is actuallythat of the class loader. Every time we refer to a class, it must handle checking whether or notthe class is loaded yet, and if it isn't, it must atomically load and initialise it. Every classcan have a piece of static class initialisation code. So we could write this:

public class MyFactory {
  private static final MyFactory instance = new MyFactory();

  public static MyFactory getInstance() {
    return instance;
  }

  private MyFactory() {}
}

or, if we wanted or needed to handle exceptions in some special way, we can (andindeed must) define an explicitstatic block:

public class MyFactory {
  private static final MyFactory instance;

  static {
    try {
      instance = new MyFactory();
    } catch (IOException e) {
      throw new RuntimeException("Darn, an error's occurred!", e);
    }
  }

  public static MyFactory getInstance() {
    return instance;
  }

  private MyFactory() throws IOException {
    // read configuration files...
  }
}

Note that if you use the first variant, it actually gets turned by the compiler into somethingresembling the second. (I've heard people say things like "I don't like static initialisers becauseof(spurious reason X)", butthere is no "magic" place to put variable initialisation in cases such as this!) However itis created, the JVM ensures that this static initialisation code is called exactly once, when the class is first referred to and loaded.

Using the class loader is generally my preferred way of dealing with lazy static initialisation. The code's nice andsimple, and I don't think you can actually do it any more efficiently. The time that it won't work of course isif for some reason you need to pass in a parameter to getInstance().

3. Use DCL plus volatile

Double-checked locking is actually OK as of Java 5 provided that you make the instancereferencevolatile. So for example, if we needed to pass in a database connectiontogetInstance(), then this would be OK:

public class MyFactory {
  private static volatile MyFactory instance;

  public static MyFactory getInstance(Connection conn)
       throws IOException {
    if (instance == null) {
      synchronized (MyFactory.class) {
        if (instance == null)
          instance = new MyFactory(conn);
      }
    }
    return instance;  
  }

  private MyFactory(Connection conn) throws IOException {
    // init factory using the database connection passed in
  }
}

Note that this is OK as of Java 5 because the definition of volatile wasspecifically changed to make it OK. Accessing a volatile variable has the semantics of synchronizationas of Java 5. In other words Java 5 ensures that the unsycnrhonized volatile read must happen afterthe write has taken place, and the reading thread will see the correct values of all fields onMyFactory.

4. Make all fields on the factoryfinal

In Java 5, a change was made to the definition of final fields. Where the values of thesefields are set in the constructor, the JVM ensures that these values are committed to main memorybefore the object reference itself. In other words, another thread that can "see" theobject cannot ever see uninitialised values of itsfinal fields. In thatcase, we wouldn't actually need to declare the instance reference as volatile.


你可能感兴趣的:(Double-checked Locking (DCL) and how to fix it)