`
LucasLee
  • 浏览: 206223 次
  • 性别: 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的常量优化的如此彻底啊。

相关推荐

    43丨单例模式(下):如何设计实现一个集群环境下的分布式单例模式?1

    单例模式是一种设计模式,旨在确保一个类只有一个实例,并提供全局访问点。在单例模式中,类的构造函数是私有的,防止外部直接创建对象,而是通过静态方法获取该类的唯一实例。单例模式的唯一性通常是在进程范围内,...

    设计模式单例模式和工厂模式综合应用

    "设计模式单例模式和工厂模式综合应用"的主题聚焦于两种常用的设计模式:单例模式和工厂模式,并探讨它们如何协同工作来实现高效、灵活的代码结构。这个主题尤其适用于Java编程语言,因为Java的面向对象特性使得设计...

    设计模式之单例模式(结合工厂模式)

    单例模式是软件设计模式中的一种经典模式,它保证了类只有一个实例存在,并提供一个全局访问点。在Java等面向对象编程语言中,单例模式常用于管理共享资源,如数据库连接池、线程池或者配置文件等。结合工厂模式,...

    C#单例模式详解 C#单例模式详解C#单例模式详解

    单例模式是软件设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。在C#中,单例模式常用于管理共享资源或控制类的实例化过程,以提高性能、节约系统资源,特别是在整个应用程序生命周期内只需要一...

    java单例模式实例

    单例模式是软件设计模式中的一种经典模式,用于确保一个类只有一个实例,并提供一个全局访问点。在Java中,有多种实现单例模式的方法,每种都有其特点和适用场景。接下来,我们将深入探讨这些实现方式。 首先,我们...

    使用C++11实现线程安全的单例模式

    在C++编程中,单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。线程安全的单例模式在多线程环境下尤其重要,因为不正确的实现可能导致多个线程创建多个实例,这违反了单例模式...

    设计模式——单例模式

    **设计模式——单例模式** 在软件工程中,设计模式是一种在特定场景下解决常见问题的标准方案,可以被复用并提升代码质量。单例模式是设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。这种模式...

    Java SE程序 单例模式

    Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式...

    7种单例模式

    单例模式是软件设计模式中的一种经典模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式在很多场景下非常有用,比如控制共享资源、管理配置对象等。下面将详细介绍七种常见的单例模式实现...

    单例模式详解~~单例模式详解~~

    单例模式是一种设计模式,它的主要目标是确保一个类只有一个实例,并提供一个全局访问点。在软件工程中,单例模式常用于控制资源的共享,比如数据库连接池...理解并正确使用单例模式对于构建高效、稳定的系统至关重要。

    使用单例模式实现计数器

    其中,单例模式是一种非常经典且常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。在C#中,我们可以利用单例模式来创建一个计数器类,以确保在整个应用程序的生命周期内,计数器只...

    c++单例模式线程日志类

    在C++编程中,单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。在这个特定的场景中,我们讨论的是一个实现了单例模式的日志类,该类专为多线程环境设计,具备日志等级控制、...

    几种单例模式demo

    单例模式是软件设计模式中的一种,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式在很多场景下都非常有用,比如控制资源的唯一性、管理共享配置或者创建昂贵的对象时避免频繁创建销毁。 ...

    Java 单例模式 工具类

    Java中的单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供全局访问点。在Java编程中,单例模式常用于控制资源的访问,比如数据库连接池、线程池或者日志对象等。本篇文章将深入探讨如何在Java中...

    单例模式应用场景

    由于该工具对于操作系统来说是独一无二且不可重复开启的,因此它符合单例模式的核心特点——确保任何时候都只有一个实例存在。这不仅简化了用户的操作体验,也提高了系统的稳定性和安全性。 **2. Windows Recycle ...

    设计模式之单例模式源码demo

    单例模式是软件设计模式中的经典模式之一,其主要目的是控制类的实例化过程,确保在应用程序的整个生命周期中,某个类只有一个实例存在。这样的设计通常适用于那些需要频繁创建和销毁,但资源消耗较大的对象,如...

    单例模式实现mdi界面子窗体控制

    首先向关注过我这个系列...这立刻让我想到了最常用也是最简单最容易理解的一个设计模式 单例模式 何为 单例模式 ? 故名思议 即 让 类 永远都只能有一个实例。 由于 示例代码 比较简单 我也加了注释,这里就不在赘述

    使用单例模式创建学生管理系统(饿汉式、懒汉式)

    单例模式是软件设计模式中的一种,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。在Java或类似的面向对象编程语言中,单例模式常用于管理共享资源,如数据库连接池、线程池或者配置文件等。在这个...

    设计模式单例模式

    单例模式是软件设计模式中的一种基础且广泛应用的模式,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式在系统中需要频繁创建和销毁对象,且对象创建成本较高,或者需要共享资源的情况下非常...

Global site tag (gtag.js) - Google Analytics