题外话:好久没写blog,做知识归纳整理了。本来设计模式就是个坑,各种文章也写烂了。不过,不是自己写的东西,缺少点知识的存在感。目前还没做到光看即能记住,得写。所以准备跳入设计模式这个大坑。

开篇先贡献给

单例模式(Singleton Pattern)

目的:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

其实单例模式应用很多,我也不陌生,有时候一些自己定义的Controller等,都会选择单例模式去实现,而本身java.lang.Runtime类的源码也使用了单例模式(Jdk7u40):

public class Runtime {

    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
  return currentRuntime;
} /** Don't let anyone else instantiate this class */
private Runtime() {} ...... }

然而,因为涉及到多线程编程,单例模式还是有不少值得注意的地方,请看下面的各种实现。

1.最简单实现:

/**
* @author YYC
* lazy-loading but NOT thread-safe
*/
public class SingletonExample { private static SingletonExample instance; private SingletonExample(){} public static SingletonExample getInstance(){
  if(instance==null){
  instance = new SingletonExample();
  }
  return instance;
}
}

这是单例模式最简单最直接的实现方法。懒汉式(lazy-loading)实现,但缺点很明显:线程不安全,不能用于多线程环境

2.同步方法实现:

/**
* @author YYC
* Thread-safe but bad performance
*/
public class SingletonExample { private static SingletonExample instance; private SingletonExample(){} public static synchronized SingletonExample getInstance(){
  if(instance==null){
  instance = new SingletonExample();
  }
   return instance;
}
}

同步getInstance()这个方法,可以保证线程安全。不过代价是性能会受到,因为大部分时间的操作其实不需要同步。

3. Double-Checked Locking实现(DCL):

/**
* @author YYC
* Double-Checked Locking
*/
public class SingletonExample { private static SingletonExample instance; private SingletonExample(){} public static SingletonExample getInstance(){
  if(instance==null){
  synchronized(SingletonExample.class){
    if(instance==null){
    instance = new SingletonExample();
    }
  }
  }
  return instance;
}
}

直接同步整个getInstance()方法产生性能低下的原因是,在判断(instance==null)时,所有线程都必须等待。而(instance==null)并非是常有情况,每次判断都必须等待,会造成阻塞。因此,有了这种双重检测的实现方法,待检查到实例没创建后(instance=null),再进行同步,然后再检查一次确保实例没创建。

在同步块里,再判定一次,是为了避免线程A准备拿到锁,而线程B创建完instance后准备释放锁的情况。如果在同步块里没有再次判定,那么线程A很可能会又创建一个实例。

另外,再引用IcyFenix文章里面的一段话,会解释清楚双锁检测的局限性:

我们来看看这个场景:假设线程一执行到instance = new SingletonExample()这句,这里看起来是一句话,但实际上它并不是一个原子操作(原子操作的意思就是这条语句要么就被执行完,要么就没有被执行过,不能出现执行了一半这种情形)。事实上高级语言里面非原子操作有很多,我们只要看看这句话被编译后在JVM执行的对应汇编代码就发现,这句话被编译成8条汇编指令,大致做了3件事情:

 

1.给SingletonExample的实例分配内存。

2.初始化SingletonExample的构造器

3.将instance对象指向分配的内存空间(注意到这步instance就非null了)。

 

但是,由于Java编译器允许处理器乱序执行(out-of-order),以及JDK1.5之前JMM(Java Memory Medel)中Cache、寄存器到主内存回写顺序的规定,上面的第二点和第三点的顺序是无法保证的,也就是说,执行顺序可能是1-2-3也可能是1-3-2,如果是后者,并且在3执行完毕、2未执行之前,被切换到线程二上,这时候instance因为已经在线程一内执行过了第三点,instance已经是非空了,所以线程二直接拿走instance,然后使用,然后顺理成章地报错,而且这种难以跟踪难以重现的错误估计调试上一星期都未必能找得出来,真是一茶几的杯具啊。

 

DCL的写法来实现单例是很多技术书、教科书(包括基于JDK1.4以前版本的书籍)上推荐的写法,实际上是不完全正确的。的确在一些语言(譬如C语言)上DCL是可行的,取决于是否能保证2、3步的顺序。在JDK1.5之后,官方已经注意到这种问题,因此调整了JMM、具体化了volatile关键字,因此如果JDK是1.5或之后的版本,只需要将instance的定义改成“private volatile static SingletonExample instance = null;”就可以保证每次都去instance都从主内存读取,就可以使用DCL的写法来完成单例模式。当然volatile或多或少也会影响到性能,最重要的是我们还要考虑JDK1.42以及之前的版本,所以本文中单例模式写法的改进还在继续。

4. 饿汉式实现(Hungry man):

/**
* @author YYC
* Hungry man. Using class loader to make it thread-safe
*/
public class SingletonExample2 { private static SingletonExample2 instance = new SingletonExample2(); private SingletonExample2(){} public static SingletonExample2 getInstance(){
  return instance;
} }

根据Java Language Specification,JVM本身保证一个类在一个ClassLoader中只会被初始化一次。那么根据classloader的这个机制,我们在类装载时就实例化,保证线程安全。

但是,有些时候,这种创建方法并不灵活。例如实例是依赖参数或者配置文件的,在getInstance()前必须调用某些方法设置它的参数。

5. 静态内部类实现(static inner class):

/**
* @author HKSCIDYX
* static inner class: make it thread-safe and lazy-loading
*/
public class SingletonExample3 { private SingletonExample3(){} public static SingletonExample3 getInstance(){
  return SingletonHolder.INSTANCE;
} private static class SingletonHolder{
  final static SingletonExample3 INSTANCE = new SingletonExample3();
} }

利用classloader保证线程安全。这种方法与第四种方法最大的区别是,就算SingletonExample3类被装载了,instance不一定被初始化,因为holder类没有被主动使用。相比而言,这种方法比第四种方法更加合理。

6. 枚举实现(Enum):

《Effective Java, 2nd》第三条:enum是实现Singleton的最佳方法

/**
* @author HKSCIDYX
* Enum
*/
public enum SingletonExample4 { INSTANCE; public void whateverMethod(){ } }

这种做法,其实还没真正在项目或者工作中见过。根据《Effective Java, 2nd》第三条,这种实现方法:

1. 简洁

2. JVM可以保证enum类的创建是线程安全(意味着其它方法的线程安全得由程序员自己去保证),

3. JVM可以无偿提供序列化机制。传统的单例模式实现方法都有个问题:一旦实现了serializable接口,他们就不再是单例的了。因为readObject()方法总会返回一个新的实例。因此为了维护并保证单例,必须声明所有实例域都是transient的,且提供一个readRevolve()方法:

/**
*
* @author HKSCIDYX
* Handle Serialized situation
*/
public class SingletonExample5 implements Serializable{ private static final long serialVersionUID = 1L; private static SingletonExample5 INSTANCE = new SingletonExample5(); //if there's other states to maintain, it must be transient private SingletonExample5(){} public static SingletonExample5 getInstance(){
  return INSTANCE;
} private Object readResolve(){
  return INSTANCE;
} }

总结

1. 单例模式,并不是整个程序或者整个应用只有一个实例,而是整个classloader只有一个实例。如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例

2. 单例模式,会使测试、找错变得困难(根据《Effective Java, 2nd,第三条》) ,尝试使用DI框架(Juice/Spring)来管理。

3. 什么情况下单例模式会失效(JPMorgan)?

Serialization, Reflection, multiple ClassLoader, multiple JVM, broken doubled checked locking(JDK4 or below) etc

参考:

《Effective Java, 2nd》

《设计模式解析,2nd》

http://icyfenix.iteye.com/blog/575052

http://xuze.me/blog/2013/01/31/singleton-pattern-seven-written/

http://837062099.iteye.com/blog/1454934

http://javarevisited.blogspot.hk/2011/03/10-interview-questions-on-singleton.html

浅谈设计模式--单例模式(Singleton Pattern)的更多相关文章

  1. 设计模式之单例模式(Singleton Pattern)

    单例模式 单例模式(Singleton Pattern)在java中算是最常用的设计模式之一,主要用于控制控制类实例的数量,防止外部实例化或者修改.单例模式在某些场景下可以提高系统运行效率.实现中的主 ...

  2. 乐在其中设计模式(C#) - 单例模式(Singleton Pattern)

    原文:乐在其中设计模式(C#) - 单例模式(Singleton Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 单例模式(Singleton Pattern) 作者:weba ...

  3. 设计模式系列之单例模式(Singleton Pattern)——确保对象的唯一性

    模式概述 模式定义 模式结构图 饿汉式单例与懒汉式单例 饿汉式单例 懒汉式单例 模式应用 模式在JDK中的应用 模式在开源项目中的应用 模式总结 主要优点 适用场景 说明:设计模式系列文章是读刘伟所著 ...

  4. 【设计模式】单例模式 Singleton Pattern

    通常我们在写程序的时候会碰到一个类只允许在整个系统中只存在一个实例(Instance)  的情况, 比如说我们想做一计数器,统计某些接口调用的次数,通常我们的数据库连接也是只期望有一个实例.Windo ...

  5. 二十四种设计模式:单例模式(Singleton Pattern)

    单例模式(Singleton Pattern) 介绍保证一个类仅有一个实例,并提供一个访问它的全局访问点. 示例保证一个类仅有一个实例. Singleton using System; using S ...

  6. 抽象工厂(Abstract Factory),工厂方法(Factory Method),单例模式(Singleton Pattern)

    在谈工厂之前,先阐述一个观点:那就是在实际程序设计中,为了设计灵活的多态代码,代码中尽量不使用new去实例化一个对象,那么不使用new去实例化对象,剩下可用的方法就可以选择使用工厂方法,原型复制等去实 ...

  7. 十次艳遇单例设计模式(Singleton Pattern)

    1.引言 单例设计模式(Singleton Pattern)是最简单且常见的设计模式之一,在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访 ...

  8. 设计模式 单例模式(Singleton) [ 转载2 ]

    设计模式 单例模式(Singleton) [ 转载2 ] @author java_my_life 单例模式的结构 单例模式的特点: 单例类只能有一个实例. 单例类必须自己创建自己的唯一实例. 单例类 ...

  9. 设计模式 单例模式(Singleton) [ 转载 ]

    设计模式 单例模式(Singleton) [ 转载 ] 转载请注明出处:http://cantellow.iteye.com/blog/838473 前言 懒汉:调用时才创建对象 饿汉:类初始化时就创 ...

随机推荐

  1. C语言。自定义函数简单版

    #include <stdio.h> //函数声明 void sayHi(); //函数实现 void sayHI() { printf("大家好!!\n"); } i ...

  2. const,static,extern简介(重要)

    一.const与宏的区别(面试题): const简介:之前常用的字符串常量,一般是抽成宏,但是苹果不推荐我们抽成宏,推荐我们使用const常量. 编译时刻:宏是预编译(编译之前处理),const是编译 ...

  3. android 之 桌面的小控件AppWidget

    AppWidget是创建的桌面窗口小控件,在这个小控件上允许我们进行一些操作(这个视自己的需要而定).作为菜鸟,我在这里将介绍一下AppWeight的简单使用. 1.在介绍AppWidget之前,我们 ...

  4. 去除UITableView中多余的分割线或者隐藏cell间的分割线

    一:去除tableView多余的分割线 首先,自定义一个方法 -(void)setExtraCellLineHidden: (UITableView *)tableView{    UIView *v ...

  5. javascript中的 类初始化,遍历for in 以及with的用法

    <script type="text/javascript"> function member(name,gender){ this.name=name; this.g ...

  6. 使用JUnit4进行java单元测试

     第一步:创建一个java工程,在工程中创建一个被单元测试的Student数据类,代码如下: package com.junittest.yu; public class Student { priv ...

  7. nodeJS创建工程

    转http://blog.csdn.net/i348018533/article/details/47258449 设置镜像地址 1.通过config命令 npm config set registr ...

  8. AFTER触发器与INSTEAD OF触发器

    在对表进行操作时,总会产生 INSERTED 和(或)DELETED表,不管这个操作是否已经进行.这里的和/或,要看进行的什么操作,插入,产生 INSERTED 表,删除,产生DELETED表,而up ...

  9. cocos2d-x之使用plist文件初试

    bool HelloWorld::init() { if ( !Layer::init() ) { return false; } FileUtils *fu=FileUtils::getInstan ...

  10. Ubuntu,QT5连接MySQL

    用QT连接MySQL需要共享库 libqsqlmysql.so的驱动,路径在plugin/sqldrivers目录下,乍看已经可用了,其实不然. 用ldd命令分析一下,libmysqlclient_r ...