论坛首页 Java企业应用论坛

单例模式与双重检测

浏览 59481 次
精华帖 (1) :: 良好帖 (10) :: 新手帖 (2) :: 隐藏帖 (6)
作者 正文
   发表时间:2010-04-26  
fengsky491 写道
dominic6988 写道
fengsky491 写道
我就想问下,为什么第三种方式,//3没执行完(也就是没有new完整),它为什么会释放锁?

 

 第三步可以看作一个赋值语句,只不过是调用构造函数初始化在付值语句之后。另外一个线程得到锁后就看到当前的instence已经不是null了就直接返回了,这个时候有可能第一个线程初始化工作做了一半,或者没有做。这样后面的线程得到的对像就会有问题。我感觉是这样的。

 

还有new的时候它为什么不会初始化完整了才,释放锁?

 

照这样理解,我是不是也可以认为第二种,在new的时候,只初始化一半,就释放了锁,其他线程进来不是一样看到的instance也不是null,而照样返回一个不完整的实例?

public class Singleton {   
 private volatile static Singleton instance = null;   
 private Singleton() {}   
 public static Singleton getInstance() {   
  if (instance == null) {   //0
   synchronized (Singleton.class) {// 1   
    if (instance == null) {// 2   
     instance = new Singleton();// 3   
    }   
   }   //4
  }   
  return instance;   
 }   
}  

 看上面的代码如果线程一执行语句3因为JVM编译器的优化工作会在构造方法实例化对像之前从构造方法返回指向该对像的引用。此时并没有执行构造方法。

然后程序执行出4此时开始执行构造方法当执行到一半的时候线程的时间片到期,此时并没有完成,线程二在0处结束等待并开始执行发现instance不为空就返回了。

我这样说你能理解吗?

0 请登录后投票
   发表时间:2010-04-26   最后修改:2010-04-26
我理解你的意思,那改成这样:
 if (instance == null) {   //0  
    synchronized (Singleton.class) {// 1     
     if (instance == null) {// 2     
      instance = new Singleton();// 3 
       
       //这里是不是只有instance初始化完整,他才会返回?
       return instance;// 4
     }     
   }


是不是就能解决问题?
0 请登录后投票
   发表时间:2010-04-26  
fengsky491 写道
我理解你的意思,那改成这样:
 if (instance == null) {   //0  
    synchronized (Singleton.class) {// 1     
     if (instance == null) {// 2     
      instance = new Singleton();// 3 
       
       //这里是不是只有instance初始化完整,他才会返回?
       return instance;// 4
     }     
   }


是不是就能解决问题?

 

 坦白的说这样真不行,因为编译通过不了,你把返回语句放在if里面,后面你还得处理返回问题。其实说白了我刚才所说的情况也只是有可能发生的。如果在释放锁之前完成对像的初始化就不会有什么问题。担是我说的那种情况也是有可能发生的,线程的问题并不是每一次测试都能发现的,至于什么时候开始初始化的工作我想只有JVM才知道吧。

 

简单概括你的问题,如果初始化发生在释放锁之前不会有什么问题,如果初始化发生在释放锁之后就有可能有问题。

0 请登录后投票
   发表时间:2010-04-26   最后修改:2010-04-26
public class Singleton {
 private volatile static Singleton instance = null;
 private Singleton() {}
 public static Singleton getInstance() {
  if (instance == null) {
   synchronized (Singleton.class) {// 1
    if (instance == null) {// 2
     instance = new Singleton();// 3

     //在这里直接返回
     return instance;
    }
   }
  }
  return instance;
 }
}
0 请登录后投票
   发表时间:2010-04-26  
fengsky491 写道
public class Singleton {
 private volatile static Singleton instance = null;
 private Singleton() {}
 public static Singleton getInstance() {
  if (instance == null) {
   synchronized (Singleton.class) {// 1
    if (instance == null) {// 2
     instance = new Singleton();// 3

     //在这里直接返回
     return instance;
    }
   }
  }
  return instance;
 }
}

我认为这样一样还有可能会有问题,此时返回的instance也是有可能没有初始化的。

 

0 请登录后投票
   发表时间:2010-04-26  
dominic6988 写道

 

 坦白的说这样真不行,因为编译通过不了,你把返回语句放在if里面,后面你还得处理返回问题。其实说白了我刚才所说的情况也只是有可能发生的。如果在释放锁之前完成对像的初始化就不会有什么问题。担是我说的那种情况也是有可能发生的,线程的问题并不是每一次测试都能发现的,至于什么时候开始初始化的工作我想只有JVM才知道吧。

 

简单概括你的问题,如果初始化发生在释放锁之前不会有什么问题,如果初始化发生在释放锁之后就有可能有问题。

 

编译是不会有问题的,因为我现在的项目就用到了第三种方式,我按上面那样该,没有问题

0 请登录后投票
   发表时间:2010-04-26  
dominic6988 写道
谢谢楼主的回复。
正如你所说的用ThreadLocal能解决这个问题,担是我认为你的代码里面的写法是不是有问题。
public class Singleton {
private static final ThreadLocal perThreadInstance = new ThreadLocal();
private static Singleton singleton ;
private Singleton() {}

public static Singleton  getInstance() {
  if (perThreadInstance.get() == null){
   // 每个线程第一次都会调用
   createInstance();
  }
  return singleton;
}

private static  final void createInstance() {
  synchronized (Singleton.class) {
   if (singleton == null){
    singleton = new Singleton();
   }
  }
  perThreadInstance.set(perThreadInstance);
}
}

倒数第三行perThreadInstance.set(perThreadInstance);应该写成perThreadInstance.set(singleton);


除此之外每一个线程都会有一个singleton的副本这样一样会造成资源浪费。本来单态模式就是想节省资源的,这样与模式的初衷不相符吧。

还有这种ThreadLocal方式的解决方案能不能应用在分布式情形,不同的JVM,ClassLoader?


写成你那样我想也没有问题的,因为这里的ThreadLocal最主要的作用就是一个标识,它只是用来标示当前线程是否已经请求或使用过singleton实例,至于perThreadInstance.set(XX)成什么值我想并不重要,关键要只要访问过或使用过就将其值设成非null。
你所说的资源问题在这里不会有问题,不管是我的那种写法还是你建义的那种,都不会有资源问题,因为这里的键是共享的,不同的线程都会使用同一个perThreadInstance或你的singleton放入每个线程的ThreadLocalMap里面。

至于分布式问题,我真还没有考虑过,我想如果这种实现不能,那么其他方式也是有问题。

最后不同的ClassLoader肯定会有不同的singleton实例,这个可以从上面的测试可以看出来。
0 请登录后投票
   发表时间:2010-04-26  
dominic6988 写道
另外一个问题是如果用了关键字volatile就能解决双重检测的问题吗?
这样的写法是能避免无序写入的问题。因为别的线程进入不了方法体,除非当前线程释放锁。这样就能确保实例化完成。我的理解对吗?
public class Singleton {  
private volatile static Singleton instance = null;  
private Singleton() {}  
public static Singleton getInstance() {  
 
   synchronized (Singleton.class) {
    if (instance == null) {         
     instance = new Singleton();    
    }  
   }  
 
  return instance;  
}  



听说在1.5版本后修改了JMM,应该是可以的,因为加上volatile后, instance = new Singleton();  则VM一定是有序执行指定,即先实例化后赋值,所以不会出现以前先赋值后实例化的问题

另外,如果程序中使用了 synchronized 键字作用于了共享资源,则不再需要volatile来修饰共享变量,因为synchronized 具有了原子性,可见性、有序性。
0 请登录后投票
   发表时间:2010-04-26  
fengsky491 写道
我就想问下,为什么第三种方式,//3没执行完(也就是没有new完整),它为什么会释放锁?


最大的原因就是 Object o = new Object(); 这样的赋值在 JMM (Java 内存模型)里不一条指令来完成,所以表面上看上去是一条,但实质是由 一串指令 来完成,所以就有可能从 这些指令中切换到另一个线程。所以就导致双重检测失效
0 请登录后投票
   发表时间:2010-04-26  
fengsky491 写道
我理解你的意思,那改成这样:
 if (instance == null) {   //0  
    synchronized (Singleton.class) {// 1     
     if (instance == null) {// 2     
      instance = new Singleton();// 3 
       
       //这里是不是只有instance初始化完整,他才会返回?
       return instance;// 4
     }     
   }


是不是就能解决问题?



肯定是不行的,因为本质你还是有这一句 “instance = new Singleton(); ”这样的赋值语句,所以还是有可能从这条语句中中断切换到另外一线程,而不是说从“return instance;”语句中切换到另一线程,不过我也不知道 return instance; 这样的语句是不是原子性的,但至少你这种必法是起不了作用的。

最主要的一点就是,线程可能从任何点切换,这个点可能是某两个语句中,也有可能是从语句中中断,这也是完全有可能的。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics