double-check

A Variant: The Double-Checked Locking Pattern

Only for multithreaded applications


This pattern only applies to multithreaded applications. If you are not involved with multithreaded applications, you might want to skip this section. This section assumes that you have a basic understanding of multithreaded issues, including synchronization.

In a multithreaded mode, Singleton does not always work properly


A problem with the Singleton pattern may arise in multithreaded applications.

Suppose two calls to getInstance() are made at virtually the same time. This can be very bad. Consider what can happen in this case:

  1. The first thread checks to see whether the instance exists. It does not, it goes into the part of the code that will create the first instance.

  2. However, before it has done that, suppose a second thread also looks to see whether the instance member is null. Because the first thread hasn't created anything yet, the instance is still equal to null, so the second thread also goes into the code that will create an object.

  3. Both threads now perform a new on the Singleton object, thereby creating two objects.

Is this a problem? It may or may not be.

None, small, bad, or worse


  • If the Singleton is absolutely stateless, this may not be a problem.

  • If the Singleton has state, and if you expect that when one object changes the state, all other objects should see the change, then this could become a serious problem. The first thread will be interacting with a different object than all other threads do. Examples of problems likely to occur:

    - Inconsistent state between threads using the different Singleton objects.

    - If the object creates a connection, there will actually be two connections (one for each object).

    - If a counter is used, there will be two counters.

  • In C++, regardless of state, the program may create a memory leak, because it may only delete one of the objects when I have actually created two of them.

It may be very difficult to find these problems. First of all, the dual creation is very intermittent—it usually won't happen. Second, it may not be obvious why the counts are off, because only one client object will contain one of the Singleton objects while all the other client objects will refer to the other Singleton.

Synchronizing the creation of the Singleton object


At first, it appears that all I need to do is synchronize the test for whether the Singleton object has been created. The only problem is that this synchronization may end up being a severe bottleneck, because all the threads will have to wait for the check on whether the object already exists.

Perhaps instead I could put some synchronization code in after the if (instance== null) test. This will not work either. Because it would be possible that both calls could meet the null test and then attempt to synchronize, I could still end up making two Singleton objects, making them one at a time.

A simple solution: Double-checked locking


The solution is to do a "sync" after the test for null and then check again to make sure the instance member has not yet been created. I show this in Example 21-3. This is called double-checked locking.[3] The intent is to optimize away unnecessary locking. This synchronization check happens at most one time, so it will not be a bottleneck.

[3] Martin, R., Riehle, D., Buschmann, F., Pattern Language of Program Design, Boston: Addison-Wesley, 1998, p. 363.

The features of double-checked locking are as follows:

  • Unnecessary locking is avoided by by adding another test before creating the object

Support for multithreaded environments.

Example 21-3. Java Code Fragment: Instantiation Only
public class USTax extends Tax {
   private static USTax instance;
   private USTax() { }
   public static Tax getInstance() {
      if (instance== null) {
         synchronized(this) {
            if (instance == null)
               instance = new USTax();
         }
      }
      return instance;
   }
   private synchronized static void doSync() {
      if (instance == null)instance = new USTax();
   }
}

Unfortunately, this doesn't work either


When the Double-Checked Locking pattern was converted from C++ to Java, it was soon discovered that it did not work for Java.[4] The reason is that the Java compiler, in its optimization efforts, may return the reference to an object from a constructor before the constructor is through instantiating the object. Thus, dosync may complete before USTax is actually completely built. This may cause a problem. Also an optimizing compiler may "notice" that there is "no way" for the "instance" member to change state between the two "if" statements, and optimize the second one out. You can avoid this problem in C# by using the keyword volatile.

[4] See the book's Web site at http://www.netobjectives.com/dpexplained for more information on this.

A Solution for Java


It is not all that hard to fix this—not that I figured it out on my own. One solution is to take advantage of the class loader, as shown in Example 21-4.

Example 21-4. Java Code Fragment: Instantiation Only
public class USTax extends Tax {
   private static class Instance {
      static final Tax instance= new USTax();
   }
   private static USTax instance;
   private USTax() { }
   public static Tax getInstance() {
      return Instance.instance;
   }
}

This works because the inner class (Instance) will get loaded only once; therefore, only one USTax object is built.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章