有关“双重检查锁定失效”的说明

双重检查锁定(以下称为DCL)已被广泛当做多线程环境下延迟初始化的一种高效手段。

遗憾的是,在Java中,如果没有额外的同步,它并不可靠。在其它语言中,如c++,实现DCL,需要依赖于处理器的内存模型、编译器实行的重排序以及编译器与同步库之间的交互。由于c++没有对这些做出明确规定,很难说DCL是否有效。可以在c++中使用显式的内存屏障来使DCL生效,但Java中并没有这些屏障。

来看下面的代码

01 // Single threaded version
02 class Foo {
03   private Helper helper = null;
04   public Helper getHelper() {
05     if (helper == null)
06         helper = new Helper();
07     return helper;
08     }
09   // other functions and members...
10 }

如果这段代码用在多线程环境下,有几个可能出错的地方。最明显的是,可能会创建出两或多个Helper对象。(后面会提到其它问题)。将getHelper()方法改为同步即可修复此问题。

01 // Correct multithreaded version
02 class Foo {
03   private Helper helper = null;
04   public synchronized Helper getHelper() {
05     if (helper == null)
06         helper = new Helper();
07     return helper;
08     }
09   // other functions and members...
10 }

上面的代码在每次调用getHelper时都会执行同步操作。DCL模式旨在消除helper对象被创建后还需要的同步。

01 // Broken multithreaded version
02 // "Double-Checked Locking" idiom
03 class Foo {
04   private Helper helper = null;
05   public Helper getHelper() {
06     if (helper == null)
07       synchronized(this) {
08         if (helper == null)
09           helper = new Helper();
10       }   
11     return helper;
12     }
13   // other functions and members...
14 }

不幸的是,这段代码无论是在优化型的编译器下还是在共享内存处理器中都不能有效工作。

不起作用

上面代码不起作用的原因有很多。接下来我们先说几个比较显而易见的原因。理解这些之后,也许你想找出一种方法来“修复”DCL模式。你的修复也不会起作用:这里面有很微妙的原因。在理解了这些原因之后,可能想进一步进行修复,但仍不会正常工作,因为存在更微妙的原因。

很多聪明的人在这上面花费了很多时间。除了在每个线程访问helper对象时执行锁操作别无他法。

不起作用的第一个原因

最显而易见的原因是,Helper对象初始化时的写操作与写入helper字段的操作可以是无序的。这样的话,如果某个线程调用getHelper()可能看到helper字段指向了一个Helper对象,但看到该对象里的字段值却是默认值,而不是在Helper构造方法里设置的那些值。

如果编译器将调用内联到构造方法中,那么,如果编译器能证明构造方法不会抛出异常或执行同步操作,初始化对象的这些写操作与hepler字段的写操作之间就能自由的重排序。

即便编译器不对这些写操作重排序,在多处理器上,某个处理器或内存系统也可能重排序这些写操作,运行在其它 处理器上的线程就可能看到重排序带来的结果。

Doug Lea写了一篇更详细的有关编译器重排序的文章。

展示其不起作用的测试案例

Paul Jakubik找到了一个使用DCL不能正常工作的例子。下面的代码做了些许整理:

001 public class DoubleCheckTest
002 {
003    
004  
005   // static data to aid in creating N singletons
006   static final Object dummyObject = new Object(); // for reference init
007   static final int A_VALUE = 256// value to initialize 'a' to
008   static final int B_VALUE = 512// value to initialize 'b' to
009   static final int C_VALUE = 1024;
010   static ObjectHolder[] singletons;  // array of static references
011   static Thread[] threads; // array of racing threads
012   static int threadCount; // number of threads to create
013   static int singletonCount; // number of singletons to create
014    
015  
016   static volatile int recentSingleton;
017  
018  
019   // I am going to set a couple of threads racing,
020   // trying to create N singletons. Basically the
021   // race is to initialize a single array of
022   // singleton references. The threads will use
023   // double checked locking to control who
024   // initializes what. Any thread that does not
025   // initialize a particular singleton will check
026   // to see if it sees a partially initialized view.
027   // To keep from getting accidental synchronization,
028   // each singleton is stored in an ObjectHolder
029   // and the ObjectHolder is used for
030   // synchronization. In the end the structure
031   // is not exactly a singleton, but should be a
032   // close enough approximation.
033   //
034  
035  
036   // This class contains data and simulates a
037   // singleton. The static reference is stored in
038   // a static array in DoubleCheckFail.
039   static class Singleton
040     {
041     public int a;
042     public int b;
043     public int c;
044     public Object dummy;
045  
046     public Singleton()
047       {
048       a = A_VALUE;
049       b = B_VALUE;
050       c = C_VALUE;
051       dummy = dummyObject;
052       }
053     }
054  
055   static void checkSingleton(Singleton s, int index)
056     {
057     int s_a = s.a;
058     int s_b = s.b;
059     int s_c = s.c;
060     Object s_d = s.dummy;
061     if(s_a != A_VALUE)
062       System.out.println("[" + index + "] Singleton.a not initialized " +
063 s_a);
064     if(s_b != B_VALUE)
065       System.out.println("[" + index
066                          "] Singleton.b not intialized " + s_b);
067      
068     if(s_c != C_VALUE)
069       System.out.println("[" + index
070                          "] Singleton.c not intialized " + s_c);
071      
072     if(s_d != dummyObject)
073       if(s_d == null)
074         System.out.println("[" + index
075                            "] Singleton.dummy not initialized,"
076                            " value is null");
077       else
078         System.out.println("[" + index
079                            "] Singleton.dummy not initialized,"
080                            " value is garbage");
081     }
082  
083   // Holder used for synchronization of
084   // singleton initialization.
085   static class ObjectHolder
086     {
087     public Singleton reference;
088     }
089  
090   static class TestThread implements Runnable
091     {
092     public void run()
093       {
094       for(int i = 0; i < singletonCount; ++i)
095         {
096     ObjectHolder o = singletons[i];
097         if(o.reference == null)
098           {
099           synchronized(o)
100             {
101             if (o.reference == null) {
102               o.reference = new Singleton();
103           recentSingleton = i;
104           }
105             // shouldn't have to check singelton here
106             // mutex should provide consistent view
107             }
108           }
109         else {
110           checkSingleton(o.reference, i);
111       int j = recentSingleton-1;
112       if (j > i) i = j;
113       }
114         }
115       }
116     }
117  
118   public static void main(String[] args)
119     {
120     if( args.length != 2 )
121       {
122       System.err.println("usage: java DoubleCheckFail" +
123                          " ");
124       }
125     // read values from args
126     threadCount = Integer.parseInt(args[0]);
127     singletonCount = Integer.parseInt(args[1]);
128      
129     // create arrays
130     threads = new Thread[threadCount];
131     singletons = new ObjectHolder[singletonCount];
132  
133     // fill singleton array
134     for(int i = 0; i < singletonCount; ++i)
135       singletons[i] = new ObjectHolder();
136  
137     // fill thread array
138     for(int i = 0; i < threadCount; ++i)
139       threads[i] = new Thread( new TestThread() );
140  
141     // start threads
142     for(int i = 0; i < threadCount; ++i)
143       

你可能感兴趣的:(java基础知识)