单例模式与双重检测 part2

本文深入探讨了单例模式在Java中的实现细节,特别是针对双重检查锁定机制的讨论。分析了不同实现方式下可能遇到的问题,如并发环境下的实例化安全性,并提供了改进方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

11 楼 fengsky491 2010-04-26   引用
我理解你的意思,那改成这样:
Java代码 复制代码
  1. if (instance == null) {   //0     
  2.    synchronized (Singleton.class) {// 1        
  3.     if (instance == null) {// 2        
  4.      instance = new Singleton();// 3    
  5.          
  6.       //这里是不是只有instance初始化完整,他才会返回?   
  7.       return instance;// 4   
  8.     }        
  9.   }  
 if (instance == null) {   //0  
    synchronized (Singleton.class) {// 1     
     if (instance == null) {// 2     
      instance = new Singleton();// 3 
       
       //这里是不是只有instance初始化完整,他才会返回?
       return instance;// 4
     }     
   }


是不是就能解决问题?
10 楼 dominic6988 2010-04-26   引用
fengsky491 写道
dominic6988 写道
fengsky491 写道
我就想问下,为什么第三种方式,//3没执行完(也就是没有new完整),它为什么会释放锁?

 

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

 

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

 

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

Java代码 复制代码
  1. public class Singleton {      
  2.  private volatile static Singleton instance = null;      
  3.  private Singleton() {}      
  4.  public static Singleton getInstance() {      
  5.   if (instance == null) {   //0   
  6.    synchronized (Singleton.class) {// 1      
  7.     if (instance == null) {// 2      
  8.      instance = new Singleton();// 3      
  9.     }      
  10.    }   //4   
  11.   }      
  12.   return instance;      
  13.  }      
  14. }    
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不为空就返回了。

我这样说你能理解吗?

9 楼 fengsky491 2010-04-26   引用
dominic6988 写道
fengsky491 写道
我就想问下,为什么第三种方式,//3没执行完(也就是没有new完整),它为什么会释放锁?

 

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

 

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

 

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

8 楼 dominic6988 2010-04-26   引用
其实个人认为用单例模式主要还是看所创建的对像是不是有状态的,如果是没有状态的就是多创建一个也不会对系统照成大的影响,但是如果是有状态的比如连接(本来是一个连接的结果变成了两个)或者计数器之类的所有线程要共享的这样的情况就不用能ThreadLocal这种方式了,这种方式是每个线程都捅有一个对像的副本且不会相互影响的,如果是这种情况就可以用关键字volatile来解决,或者通过静态内部类(第五种)的方式保证只加载一次来实现单态。
总之楼主总结的不错。能用的这几种方式都总结出来了。
7 楼 玲cc 2010-04-26   引用
恩,楼上说的没错
是因为这样
6 楼 dominic6988 2010-04-26   引用
fengsky491 写道
我就想问下,为什么第三种方式,//3没执行完(也就是没有new完整),它为什么会释放锁?

 

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

5 楼 fengsky491 2010-04-26   引用
我就想问下,为什么第三种方式,//3没执行完(也就是没有new完整),它为什么会释放锁?
4 楼 dominic6988 2010-04-26   引用
另外一个问题是如果用了关键字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;  
}  

3 楼 dominic6988 2010-04-26   引用
谢谢楼主的回复。
正如你所说的用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?
2 楼 junJZ_2008 2010-04-26   引用
dominic6988 写道
楼主能详细说明第二种加载和第三种的区别吗?
我认为第二种方法同步方法加锁对像是this,而第三种加载方式是同步当前类的类对像。所以单从范围上面来讲只是if (instance == null)这一句的区别。


第二种加锁的对象不是this,其实也是 Singleton.class 锁对象,以前我测试过。为什么说是Singleton.class而不是this呢,最简单的理由就是该方法是静态的,这就很明显了:静态方法是不能访问this的。
其实第二也等同于下在面:
Java代码 复制代码
  1. public class Singleton {   
  2.  private volatile static Singleton instance = null;   
  3.  private Singleton() {}   
  4.  public static Singleton getInstance() {   
  5.   
  6.    synchronized (Singleton.class) {   
  7.     if (instance == null) {   
  8.      instance = new Singleton();   
  9.     }   
  10.    }   
  11.   
  12.   return instance;   
  13.  }   
  14. }  
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;
 }
}


第二种与第三种唯一的区别就是第三种多了“instance == null”的条件,但该条件的检测不是放在同步块中的,正是因为这一点,导致了双重检测失效!
1 楼 dominic6988 2010-04-26   引用
楼主能详细说明第二种加载和第三种的区别吗?
我认为第二种方法同步方法加锁对像是this,而第三种加载方式是同步当前类的类对像。所以单从范围上面来讲只是if (instance == null)这一句的区别。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值