题外话:好久没写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. 全球最低功耗蓝牙单芯片DA14580的硬件架构和低功耗

    号称全球最低功耗蓝牙单芯片DA14580在可穿戴市场.健康医疗.ibeacon定位等市场得到广泛的应用,但是因为其较为封闭的技术/资料支持导致开发人员有较高的技术门槛,网络上也极少看到有关DA1458 ...

  2. iOSQuartz2D-01-核心要点

    简介 作用 绘制 绘制图形 : 线条\三角形\矩形\圆\弧等 绘制文字 绘制\生成图片(图像) 读取\生成PDF 截图\裁剪图片 自定义UI控件(通常为内部结构较复杂的控件) UIKit中的绝大部分控 ...

  3. SQL Server智能感知如何更新

    经常用sql server发现一个问题,比如说我刚刚添加个表或者字段,这时候在sqlserver里面写sql语句时,没有智能提示,这个问题我以前一直不是太注意.今天好好找了下解决方法,这里做下分享. ...

  4. Http协议中 常用的参数应用

    1 请求来自哪一个页面 request.getHeader("referer"); 在购买页,通过a标签进入AddressAction中,地址保存后,需要跳到原先的页面. 另外,另 ...

  5. Asp.net Identity 2.0 作弊条

    Moving ASP.NET Identity model to class library http://stackoverflow.com/questions/23446919/moving-as ...

  6. 第七篇 :微信公众平台开发实战Java版之如何获取微信用户基本信息

    在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的.对于不同公众号,同一用户的openid不同). 公众号可通过本接口来根据O ...

  7. nyoj 38 布线问题

    题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=38 最小生成树水题~ 代码: #include "stdio.h" / ...

  8. hdu 3987 Harry Potter and the Forbidden Forest 求割边最少的最小割

    view code//hdu 3987 #include <iostream> #include <cstdio> #include <algorithm> #in ...

  9. php类型转换以及类型转换的判别

    部分摘自PHP: 类型 - Manual 相关链接 PHP 在变量定义中不需要(或不支持)明确的类型定义:变量类型是根据使用该变量的上下文所决定的.也就是说,如果把一个 string 值赋给变量 $v ...

  10. [转]GridView中直接新增行、编辑和删除

    本文转自:http://www.cnblogs.com/gdjlc/archive/2009/11/10/2086951.html .aspx <div><asp:Button ru ...