`
LucasLee
  • 浏览: 206564 次
  • 性别: 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();之后才赋值以确认初始化完成。
这样不是既可保持高性能(绝大部分情况下没有锁,不进入需同步的块)、又可保证线程安全么?
分享到:
评论
23 楼 icewubin 2008-07-08  
lt0604 写道
单例在分布式应用中应该特别注意!


分布式应用中,单例干脆就别用了,即使用了也就是节省点内存的用途了。
22 楼 dennis_zane 2008-07-07  
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
21 楼 LucasLee 2008-07-07  
icewubin 写道

但是
public static final Singleton INSTANCE=new Singleton();

这种模式的话,对这个类的任何访问都会触发,比如这个类有什么全局常量的访问,也会触发初始化,但是此时根本不需要初始化。


你这种说法不知道有什么证据?
我以前也和你一样,想当然这样认为“对这个类的任何访问都会触发,比如这个类有什么全局常量的访问,也会触发初始化”,但我做了个小实验,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常量是不会触发此类其他静态变量的初始化的,(即单例也不会被初始化)。
调用静态方法则会。

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


试试这样
19 楼 lt0604 2008-07-07  
单例在分布式应用中应该特别注意!
18 楼 edwardpro 2008-07-07  
synchronized  的确实一种落后的声明了...
17 楼 icewubin 2008-07-06  
Mozart 写道
Initialization on Demand Holder 好像是较早google的BOB提出的,也就是Guice的作者。其实Singleton说开了有很多可能导致意外多实例的情况。不同的VM,分布式应用比如EJB,RMI里面都可能出现假Singleton,不同的Class loader,比如applet在加载不同地址代码的时候可能发生意外,还有同步控制错误等。要防范所有的情况还真比较困难,个人看法最好还是让Singleton stateless,只负责对资源的存取,以策万全。
因为既便是很好防止了上面提到的种种可能,GC也可能制造一些麻烦(以为没人用就回收了,后来发现又有人要,只得重新创建一个Singleton,有状态也变没状态了)



Singleton的应用比较狭窄,如果出现如上场景,Singleton多半就是反模式,还不如不用,一般也就是放些不重要的,或者明显无状态的功能。
16 楼 香克斯 2008-07-06  
buaawhl 写道
以前有人讨论过。
一种比较好的实现方式是Initialization on Demand Holder (IODH):

public   class  Singleton  {   
   static  class  SingletonHolder  { 
     static  Singleton instance  =   new  Singleton();   
   }
  
   public   static  Singleton getInstance()  {   
     return  SingletonHolder.instance;   
  }    
}


能够达到Lazy Singleton的效果,即只有需要的时候,才初始化,而且只需要初始化一次。
这是依靠JVM对内部静态类&静态成员初始化的顺序机制来实现的。
这种实现方法虽然有一定的局限性,比如,只能用于静态成员,ClassLoader要确定等等,但是这种实现方法已经足够好了。


c#的msdn上面是这种方法.java上面的机制应该也是可以的.虽然static属性是第一次使用类的时候初始化,但是这种子class的应该是第一次使用这个子的class的时候才进行初始化的吧.

还有那个使用0,1来进行二次检测的方法.我觉得应该是可行的.使用元数据类型的话不存在创建时间之类的问题.true,false应该也是可行的.

不知道反对的人具体的什么原因呢?

15 楼 ln1058 2008-07-06  
是不是考虑一下用ThreadLocal会更好呢,虽然不是单例模式
14 楼 spiritfrog 2008-07-06  
这贴为什么给新手帖这么多?
难道都理解了?
ibm这篇http://www.ibm.com/developerworks/java/library/j-dcl.html
out-of-order writes往后,就很难看下去了。
13 楼 Mozart 2008-07-06  
Initialization on Demand Holder 好像是较早google的BOB提出的,也就是Guice的作者。其实Singleton说开了有很多可能导致意外多实例的情况。不同的VM,分布式应用比如EJB,RMI里面都可能出现假Singleton,不同的Class loader,比如applet在加载不同地址代码的时候可能发生意外,还有同步控制错误等。要防范所有的情况还真比较困难,个人看法最好还是让Singleton stateless,只负责对资源的存取,以策万全。
因为既便是很好防止了上面提到的种种可能,GC也可能制造一些麻烦(以为没人用就回收了,后来发现又有人要,只得重新创建一个Singleton,有状态也变没状态了)
12 楼 icewubin 2008-07-06  
Lucas Lee 写道
buaawhl 写道
以前有人讨论过。
一种比较好的实现方式是Initialization on Demand Holder (IODH):

public   class  Singleton  {   
   static  class  SingletonHolder  { 
     static  Singleton instance  =   new  Singleton();   
   }
  
   public   static  Singleton getInstance()  {   
     return  SingletonHolder.instance;   
  }    
}


能够达到Lazy Singleton的效果,即只有需要的时候,才初始化,而且只需要初始化一次。
这是依靠JVM对内部静态类&静态成员初始化的顺序机制来实现的。
这种实现方法虽然有一定的局限性,比如,只能用于静态成员,ClassLoader要确定等等,但是这种实现方法已经足够好了。

我觉得这种方法跟我提到的第一种方法没有什么区别,就是
public static final Singleton INSTANCE=new Singleton();

你是不是说这种方法不是lazy init的?但我试验的结果就是laze的。
即static变量不是在class加载的时候初始化的,而是在实例化此类第一个实例的时候初始化的。
不知道这是不是JVM的标准行为。


lazy只是个说法,lazy的程度是不一样的。

举例如下:
IODH模式下,只有在调用getInstance()的时候才会发生初始化。
但是
public static final Singleton INSTANCE=new Singleton();

这种模式的话,对这个类的任何访问都会触发,比如这个类有什么全局常量的访问,也会触发初始化,但是此时根本不需要初始化。
11 楼 LucasLee 2008-07-06  
buaawhl 写道
以前有人讨论过。
一种比较好的实现方式是Initialization on Demand Holder (IODH):

public   class  Singleton  {   
   static  class  SingletonHolder  { 
     static  Singleton instance  =   new  Singleton();   
   }
  
   public   static  Singleton getInstance()  {   
     return  SingletonHolder.instance;   
  }    
}


能够达到Lazy Singleton的效果,即只有需要的时候,才初始化,而且只需要初始化一次。
这是依靠JVM对内部静态类&静态成员初始化的顺序机制来实现的。
这种实现方法虽然有一定的局限性,比如,只能用于静态成员,ClassLoader要确定等等,但是这种实现方法已经足够好了。

我觉得这种方法跟我提到的第一种方法没有什么区别,就是
public static final Singleton INSTANCE=new Singleton();

你是不是说这种方法不是lazy init的?但我试验的结果就是laze的。
即static变量不是在class加载的时候初始化的,而是在实例化此类第一个实例的时候初始化的。
不知道这是不是JVM的标准行为。
10 楼 buaawhl 2008-07-05  
以前有人讨论过。
一种比较好的实现方式是Initialization on Demand Holder (IODH):

public   class  Singleton  {   
   static  class  SingletonHolder  { 
     static  Singleton instance  =   new  Singleton();   
   }
  
   public   static  Singleton getInstance()  {   
     return  SingletonHolder.instance;   
  }    
}


能够达到Lazy Singleton的效果,即只有需要的时候,才初始化,而且只需要初始化一次。
这是依靠JVM对内部静态类&静态成员初始化的顺序机制来实现的。
这种实现方法虽然有一定的局限性,比如,只能用于静态成员,ClassLoader要确定等等,但是这种实现方法已经足够好了。
9 楼 LucasLee 2008-07-05  
yxbbing 写道
# private static Singleton INSTANCE; 
# public static Singleton getInstance(){ 
#   if(INSTANCE==null){ 
#     synchronized(Singelton.class){ 
#       //Double checking 
#       if(INSTANCE==null){ 
#         INSTANCE=new Singleton(); 
#       } 
#     } 
#   } 
# }
 

1. private static int hasInitialized=0; 
   2. private static Singleton INSTANCE; 
   3. public static Singleton getInstance(){ 
   4.   if(hasInitialized==0){ 
   5.     synchronized(Singelton.class){ 
   6.       //Double checking 
   7.       if(hasInitialized==0){ 
   8.         INSTANCE=new Singleton(); 
   9.         hasInitialized=1; 
  10.       } 
  11.     } 
  12.   } 
  13. } 

差不多吗


你没有仔细研究第一个方法为什么有问题,及第二种方法为什么解决了它的问题,当然会觉得差不多了。
8 楼 yxbbing 2008-07-04  
# private static Singleton INSTANCE; 
# public static Singleton getInstance(){ 
#   if(INSTANCE==null){ 
#     synchronized(Singelton.class){ 
#       //Double checking 
#       if(INSTANCE==null){ 
#         INSTANCE=new Singleton(); 
#       } 
#     } 
#   } 
# }
 

1. private static int hasInitialized=0; 
   2. private static Singleton INSTANCE; 
   3. public static Singleton getInstance(){ 
   4.   if(hasInitialized==0){ 
   5.     synchronized(Singelton.class){ 
   6.       //Double checking 
   7.       if(hasInitialized==0){ 
   8.         INSTANCE=new Singleton(); 
   9.         hasInitialized=1; 
  10.       } 
  11.     } 
  12.   } 
  13. } 

差不多吗
7 楼 LucasLee 2008-07-04  
biubiu 写道
除了你已经提出的两种办法,没有其他可以run everywhere的办法了。

我最后提出的一种方案不行么?
6 楼 LucasLee 2008-07-04  
slangmgh 写道

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

这个不会吧?你的证据呢?
我提到的out-of-order write只是对INSTANCE=new Singleton()范围而言。
连两条语句的顺序都无法保证,这个就难以想象了。。。
5 楼 LucasLee 2008-07-04  
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; 
      } 
    } 
  } 


谢谢提醒,我已经修改了,笔误。
4 楼 biubiu 2008-07-04  
除了你已经提出的两种办法,没有其他可以run everywhere的办法了。

相关推荐

    二十三种设计模式【PDF版】

    主要是介绍各种格式流行的软件设计模式,对于程序员的进一步提升起推进作用,有时间可以随便翻翻~~ 23种设计模式汇集 如果你还不了解设计模式是什么的话? 那就先看设计模式引言 ! 学习 GoF 设计模式的重要性 ...

    FrameWorkSrc源码

    - **理解设计模式**:源码中会应用各种设计模式,如工厂模式、单例模式、观察者模式等,学习这些模式可以提升代码质量。 - **优化代码**:了解框架的性能瓶颈和优化手段,有助于编写更高效的代码。 - **定制化...

    桌面 宠物 c.net 仅供学习

    7. **设计模式**: 在编写复杂逻辑时,设计模式(如观察者模式、单例模式)可以帮助我们编写出更清晰、可维护的代码。 通过深入研究这个名为“QQpet”的压缩包中的源代码,学习者不仅可以掌握C#.NET的基本语法,还能...

    Notes:笔记应用使用会议室数据库

    接着,使用单例模式提供数据库实例,确保整个应用生命周期内只有一个数据库实例。这样可以避免资源浪费,提高性能。 在实际应用中,我们需要考虑以下几点: 1. 数据库迁移:当应用升级,数据库结构可能发生变化,...

    Demo:平时看书学习写的Demo

    8. **设计模式**:单例、工厂、观察者等常见设计模式的应用。 9. **JDBC**:与数据库交互,如连接、查询、更新数据。 10. **API使用**:如Java内置的Math库,日期时间API,或者其他第三方库如Apache Commons、Google...

Global site tag (gtag.js) - Google Analytics