`
LucasLee
  • 浏览: 204796 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

对于单例模式的一点想法

阅读更多
单例模式很普遍,对于Spring的实现机制不清楚,单就Java语言上的实现机制来讨论。
虽然简单,但要获得一个高性能且线程安全的单例确不简单。
最简单的、成熟的单例实现有如下两种:
1.
public static final Singleton INSTANCE=new Singleton();

即在声明静态变量时就实例化。这种方法的问题是,不能传入构造参数从而动态的创建实例。
2.
public static synchronized Singleton getInstance(){...}

即在方法上同步。这种方法的问题是,始终有同步的开销(虽然对很多应用来说这开销并不大,以致不需要考虑),而更理想的情况是,读操作不需要同步,只在创建实例时同步。
看上去更好的方法(但有问题!)是:
Double-checked synchronization,
如:
private static Singleton INSTANCE;
public static Singleton getInstance(){
  if(INSTANCE==null){
    synchronized(Singelton.class){
      //Double checking
      if(INSTANCE==null){
        INSTANCE=new Singleton();
      }
    }
  }
}

问题解释如下:
参考1:http://www.ibm.com/developerworks/java/library/j-dcl.html
参考2:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
在参考1中提到,out-of-order writes是原因,就是说INSTANCE=new Singleton();这行代码并不是一定按如下伪代码顺序进行的:
1.分配内存
2.调用构造器
3.赋值给INSTANCE

在有的JIT上会编译优化为:
1.分配内存
2.赋值给INSTANCE
3.调用构造器

这就是所谓的out-of-order writes。则问题会出在第2步:此时判断(INSTANCE==null)已经返回真了,但构造器还未调用完成,此时访问INSTANCE则会出现不可预料的问题。
以上都是简单的重复广为人知的知识,下面是我的补充:
在参考2中的"It will work for 32-bit primitive values"一节给了我启发,它提到对32位的原始类型的Double-checked locking是可以的,(我认为实际关键点在于
赋值操作是否是原子的)。既然对int的赋值是原子的,我们可以稍加改进,引入一个int hasInitialized:
private static int hasInitialized=0;
private static Singleton INSTANCE;
public static Singleton getInstance(){
  if(hasInitialized==0){
    synchronized(Singelton.class){
      //Double checking
      if(hasInitialized==0){
        INSTANCE=new Singleton();
        hasInitialized=1;
      }
    }
  }
}

区别在于:
以hasInitialized==0来判断是否初始化完成,而在NSTANCE=new Singleton();之后才赋值以确认初始化完成。
这样不是既可保持高性能(绝大部分情况下没有锁,不进入需同步的块)、又可保证线程安全么?
分享到:
评论
43 楼 lzy.je 2008-08-22  
DCL成熟的
42 楼 okhaoba 2008-08-22  
我觉得懒汉模式,即第一种足矣。
41 楼 insiku 2008-07-16  
想不到javaeye上 不看清楚就乱回帖的人也这么多
40 楼 icewubin 2008-07-15  
还在讨论synchronized?第二页buaawhl 说的那个Initialization on Demand Holder (IODH),不是非常好的解决方案么?
比“饿汉式”更充分的lazy,比“双重检查”或“懒汉式(同步)”性能更好,代码也简捷。
39 楼 xin_feng_08 2008-07-15  
Lucas Lee 写道
weiqingfei 写道
private static int hasInitialized=0; 
private static Singleton INSTANCE; 
public static synchronized Singleton getInstance(){ 
  if(hasInitialized==0){ 
    synchronized(Singelton.class){ 
      //Double checking 
      if(hasInitialized==0){ 
        INSTANCE=new Singleton(); 
        hasInitialized=1; 
      } 
    } 
  } 


谢谢提醒,我已经修改了,笔误。



我个人觉得还是不要把synchronized放在方法头, 这样子每次调用把个方法都要先锁一下感觉很别扭
38 楼 LucasLee 2008-07-15  
qfs_v 写道

不要是试图使用双重检查来 解决这个问题。
在java与模式中作者讲得很明白了。

在多线程的程序中,只能尽量降低同步的几率来保证单例的唯一性。

下面这这样就就是基于上面的理由。

	
         private static HashCodeDigest instance = null;

	private HashCodeDigest() {
	}

	private static synchronized void syncInit() {
		if (instance == null) {
			instance = new HashCodeDigest();
		}
	}

	public static HashCodeDigest getInstance() {
		if (instance == null) {
			syncInit();
		}
		return instance;
	}


而这样做你是完全依赖于双重检查。

private static Singleton INSTANCE;   
public static Singleton getInstance(){   
  if(INSTANCE==null){   
    synchronized(Singelton.class){   
      //Double checking   
      if(INSTANCE==null){   
        INSTANCE=new Singleton();   
      }   
    }   
  }   
} 


我已经说的很清楚了,你这个就是变相的doublechecking。实际上没有本质区别。
你的问题在于,instance == null这个是不安全的。从前面的参考资料中可以得知,instance = new HashCodeDigest();这句话可能会这样执行:
1.给instance赋值(此时不是null了!!!)
2.调用构造器。(此时才初始化,而在前一步就通过检查了,这就是问题所在)。
37 楼 lcllcl987 2008-07-15  
既然在分布式应用中不赞成使用singleton。
那么如何在分布式应用中使用某种方式, 达到类似singleton的效果?
比如我要维持一个单态instance, 此单态instance是有状态的, 因为Application需要随时通过此单态instance读取或者修改某变量(比如修改或读取此单态中的一个map)。
难道只能把状态持久化?
再次强调, 是分布式应用(EJB)。
36 楼 qfs_v 2008-07-15  

不要是试图使用双重检查来 解决这个问题。
在java与模式中作者讲得很明白了。

在多线程的程序中,只能尽量降低同步的几率来保证单例的唯一性。

下面这这样就就是基于上面的理由。

	
         private static HashCodeDigest instance = null;

	private HashCodeDigest() {
	}

	private static synchronized void syncInit() {
		if (instance == null) {
			instance = new HashCodeDigest();
		}
	}

	public static HashCodeDigest getInstance() {
		if (instance == null) {
			syncInit();
		}
		return instance;
	}


而这样做你是完全依赖于双重检查。

private static Singleton INSTANCE;   
public static Singleton getInstance(){   
  if(INSTANCE==null){   
    synchronized(Singelton.class){   
      //Double checking   
      if(INSTANCE==null){   
        INSTANCE=new Singleton();   
      }   
    }   
  }   
} 

35 楼 insiku 2008-07-15  
netrice 写道
	public static SysRoleDAO getInstance() {
		if (sysRole == null) {
			synchronized (SysRoleDAO.class) {
				if (sysRole == null) {
					SysRoleDAO _sysRole = new SysRoleDAO();
					sysRole = _sysRole;
				}
			}
		}
		return sysRole;
	}


试试这样



这个跟LZ的最后一种方法类似
只是如果JIT连sysRole = _sysRole;也优化的话 那就又有问题了
34 楼 maowc 2008-07-14  
用Enum
33 楼 runthu 2008-07-14  
单例模式用在java web问题多多。
32 楼 biubiu 2008-07-14  
Lucas Lee 写道
biubiu 写道
除了你已经提出的两种办法,没有其他可以run everywhere的办法了。

我最后提出的一种方案不行么?


看看附件的文章。最后的结论是:
“Best practice is that if a variable is ever to be assigned by one thread and used or assigned by another, then all accesses to that variable should be enclosed in synchronized methods or synchronized statements.”
31 楼 biubiu 2008-07-14  
spiritfrog 写道
这贴为什么给新手帖这么多?
难道都理解了?
ibm这篇http://www.ibm.com/developerworks/java/library/j-dcl.html
out-of-order writes往后,就很难看下去了。


因为这里的很多人很浮躁,以为这么简单的问题自己早就知道了。其实是错的。
30 楼 microjuz 2008-07-14  
Lucas Lee 写道
slangmgh 写道

JVM 不保证代码INSTANCE=new Singleton() 一定在hasInitialized=1之前执行!!!

这个不会吧?你的证据呢?
我提到的out-of-order write只是对INSTANCE=new Singleton()范围而言。
连两条语句的顺序都无法保证,这个就难以想象了。。。


确实有这个可能的,如果两条语句的顺序改变不会影响执行结果,编译器有可能为某种优化对其进行顺序调整,我在《java concurrecy in practice》中看到的。
29 楼 ellen_yang 2008-07-11  
路过,学习了
28 楼 LucasLee 2008-07-10  
lefish 写道
这样可能会好一点.
	private static HashCodeDigest instance = null;

	private HashCodeDigest() {
	}

	private static synchronized void syncInit() {
		if (instance == null) {
			instance = new HashCodeDigest();
		}
	}

	public static HashCodeDigest getInstance() {
		if (instance == null) {
			syncInit();
		}
		return instance;
	}

你这个就是我在最开始提到的普通的double-checking的做法,static synchronized method等效于synchonized(Singleton.class).
一样会有问题。
27 楼 lefish 2008-07-10  
这样可能会好一点.
	private static HashCodeDigest instance = null;

	private HashCodeDigest() {
	}

	private static synchronized void syncInit() {
		if (instance == null) {
			instance = new HashCodeDigest();
		}
	}

	public static HashCodeDigest getInstance() {
		if (instance == null) {
			syncInit();
		}
		return instance;
	}
26 楼 kaipingk 2008-07-10  
再集群环境下怎么实现单例模式?
25 楼 suntiance 2008-07-09  
dennis_zane 写道
Lucas Lee 写道
你这种说法不知道有什么证据?
我以前也和你一样,想当然这样认为“对这个类的任何访问都会触发,比如这个类有什么全局常量的访问,也会触发初始化”,但我做了个小实验,SUN JDK1.6下(JDK1.5一样):

package test;

public class Singleton {
	public static final String NAME = "test";
	private static final Singleton INSTANCE = new Singleton();

	private Singleton() {
		System.out.println("Invoke constructor.");
	}

	public static Singleton getInstance() {
		return INSTANCE;
	}
}

package test;

public class Test1 {

	public static void main(String[] args) throws Exception {
		System.out.println(Singleton.NAME);
		System.out.println("-------------------");
		Singleton.printString("Test static method.");
	}

}

执行结果如下:
test
-------------------
Invoke constructor.
Test static method.


说明调用Singleton.NAME常量是不会触发此类其他静态变量的初始化的,(即单例也不会被初始化)。
调用静态方法则会。


static final会直接编译成常量,也就是Test1中的Single.NAME其实是直接初始化成常量,并没有调用到Singleton。看看这个帖子 http://www.iteye.com/topic/211937



是这样的,访问static final不属于主动调用。
24 楼 icewubin 2008-07-08  
多谢举出反例,不过即使如此,之前说的lazy的程度不一样还是成立的。

没想到JVM对static final的常量优化的如此彻底啊。

相关推荐

Global site tag (gtag.js) - Google Analytics