java double-check-lock并发问题


// thread 1
class Singleton {
    private static Singleton INSTANCE

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton() // new对象
                }
            }
        }
        return INSTANCE;
    }

}

// thread 2
Singleton.getInstance(); // 这里可能获取到未创建完整的Singleton对象引用

这个代码是无法工作的. 因为这个可能让其他线程看到没有完全构件好的对象:

JVM创建一个对象的过程来看,分为:“装载”,“连接”,“初始化”三个步骤。
在连接步骤中包含“验证”,“准备”,“解析”这几个环节。
为一个对象分配内存的过程是在连接步骤的准备环节,它是先于“初始化”步骤的,而构造函数的执行是在“初始化”步骤中的。

简单理解,上述INSTANCE = new Singleton()的过程是非原子操作,其实际过程如下:

// 实际上的步骤:
1.allocateMemory -> object 
2.Singleton._INSTANCE = object
3.init object attributes

假如两个线程同时抢夺执行上述代码,此时因为线程1和线程2没有用同步,他们之间不存在“Happens-Before”规则的约束,所以在线程1创建Singleton对象的 1,2这两个步骤对于线程2来说会有可能出现1可见,2不可见
造成了线程2获取到了一个未创建完整的Singleton对象引用

//其中分析一个对象创建过程的部分摘抄如下[Symantec(赛门铁克)JIT]:

singletons[i].reference = new Singleton();
to the following (note that the Symantec JIT using a handle-based object allocation system).

-------
0206106A   mov         eax,0F97E78h         ; allocate space for
0206106F   call        01F6B210             ; Singleton, return result in eax
02061074   mov         dword ptr [ebp],eax       ; EBP is &singletons[i].reference 

02061077   mov         ecx,dword ptr [eax]       ; store the unconstructed object here.

02061079   mov         dword ptr [ecx],100h      ; dereference the handle to
0206107F   mov         dword ptr [ecx+4],200h    ; get the raw pointer
02061086   mov         dword ptr [ecx+8],400h   ; Next 4 lines are
0206108D   mov         dword ptr [ecx+0Ch],0F84030h ; Singleton's inlined constructor

-------

As you can see, the assignment to singletons[i].reference is performed before the constructor for Singleton is called. This is completely legal under the existing Java memory model, and also legal in C and C++ (since neither of them have a memory model).

jdk5以上版本可使用volatitle修复,jdk5以下版本基本无法修复

/*
* @since 1.5
*/
class Singleton {
    private static volatile Singleton INSTANCE

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton() // new对象
                }
            }
        }
        return INSTANCE;
    }

}

http://ifeve.com/java-concurrent-hashmap-1/

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

附: Doug Lea
public class DoubleCheckTest
  {


  // static data to aid in creating N singletons
  static final Object dummyObject = new Object(); // for reference init
  static final int A_VALUE = 256; // value to initialize 'a' to
  static final int B_VALUE = 512; // value to initialize 'b' to
  static final int C_VALUE = 1024;
  static ObjectHolder[] singletons;  // array of static references
  static Thread[] threads; // array of racing threads
  static int threadCount; // number of threads to create
  static int singletonCount; // number of singletons to create


  static volatile int recentSingleton;


  // I am going to set a couple of threads racing,
  // trying to create N singletons. Basically the
  // race is to initialize a single array of 
  // singleton references. The threads will use
  // double checked locking to control who 
  // initializes what. Any thread that does not
  // initialize a particular singleton will check 
  // to see if it sees a partially initialized view.
  // To keep from getting accidental synchronization,
  // each singleton is stored in an ObjectHolder 
  // and the ObjectHolder is used for 
  // synchronization. In the end the structure
  // is not exactly a singleton, but should be a
  // close enough approximation.
  // 


  // This class contains data and simulates a 
  // singleton. The static reference is stored in
  // a static array in DoubleCheckFail.
  static class Singleton
    {
    public int a;
    public int b;
    public int c;
    public Object dummy;

    public Singleton()
      {
      a = A_VALUE;
      b = B_VALUE;
      c = C_VALUE;
      dummy = dummyObject;
      }
    }

  static void checkSingleton(Singleton s, int index)
    {
    int s_a = s.a;
    int s_b = s.b;
    int s_c = s.c;
    Object s_d = s.dummy;
    if(s_a != A_VALUE)
      System.out.println("[" + index + "] Singleton.a not initialized " +
s_a);
    if(s_b != B_VALUE)
      System.out.println("[" + index 
                         + "] Singleton.b not intialized " + s_b);

    if(s_c != C_VALUE)
      System.out.println("[" + index 
                         + "] Singleton.c not intialized " + s_c);

    if(s_d != dummyObject)
      if(s_d == null)
        System.out.println("[" + index 
                           + "] Singleton.dummy not initialized," 
                           + " value is null");
      else
        System.out.println("[" + index 
                           + "] Singleton.dummy not initialized," 
                           + " value is garbage");
    }

  // Holder used for synchronization of 
  // singleton initialization. 
  static class ObjectHolder
    {
    public Singleton reference;
    }

  static class TestThread implements Runnable
    {
    public void run()
      {
      for(int i = 0; i < singletonCount; ++i)
        {
    ObjectHolder o = singletons[i];
        if(o.reference == null)
          {
          synchronized(o)
            {
            if (o.reference == null) {
              o.reference = new Singleton();
          recentSingleton = i;
          }
            // shouldn't have to check singelton here
            // mutex should provide consistent view
            }
          }
        else {
          checkSingleton(o.reference, i);
      int j = recentSingleton-1;
      if (j > i) i = j;
      }
        } 
      }
    }

  public static void main(String[] args)
    {
    if( args.length != 2 )
      {
      System.err.println("usage: java DoubleCheckFail" +
                         " <numThreads> <numSingletons>");
      }
    // read values from args
    threadCount = Integer.parseInt(args[0]);
    singletonCount = Integer.parseInt(args[1]);

    // create arrays
    threads = new Thread[threadCount];
    singletons = new ObjectHolder[singletonCount];

    // fill singleton array
    for(int i = 0; i < singletonCount; ++i)
      singletons[i] = new ObjectHolder();

    // fill thread array
    for(int i = 0; i < threadCount; ++i)
      threads[i] = new Thread( new TestThread() );

    // start threads
    for(int i = 0; i < threadCount; ++i)
      threads[i].start();

    // wait for threads to finish
    for(int i = 0; i < threadCount; ++i)
      {
      try
        {
        System.out.println("waiting to join " + i);
        threads[i].join();
        }
      catch(InterruptedException ex)
        {
        System.out.println("interrupted");
        }
      }
    System.out.println("done");
    }
  }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值