浅谈设计模式--单例模式(Singleton Pattern)
题外话:好久没写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)的更多相关文章
- 设计模式之单例模式(Singleton Pattern)
单例模式 单例模式(Singleton Pattern)在java中算是最常用的设计模式之一,主要用于控制控制类实例的数量,防止外部实例化或者修改.单例模式在某些场景下可以提高系统运行效率.实现中的主 ...
- 乐在其中设计模式(C#) - 单例模式(Singleton Pattern)
原文:乐在其中设计模式(C#) - 单例模式(Singleton Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 单例模式(Singleton Pattern) 作者:weba ...
- 设计模式系列之单例模式(Singleton Pattern)——确保对象的唯一性
模式概述 模式定义 模式结构图 饿汉式单例与懒汉式单例 饿汉式单例 懒汉式单例 模式应用 模式在JDK中的应用 模式在开源项目中的应用 模式总结 主要优点 适用场景 说明:设计模式系列文章是读刘伟所著 ...
- 【设计模式】单例模式 Singleton Pattern
通常我们在写程序的时候会碰到一个类只允许在整个系统中只存在一个实例(Instance) 的情况, 比如说我们想做一计数器,统计某些接口调用的次数,通常我们的数据库连接也是只期望有一个实例.Windo ...
- 二十四种设计模式:单例模式(Singleton Pattern)
单例模式(Singleton Pattern) 介绍保证一个类仅有一个实例,并提供一个访问它的全局访问点. 示例保证一个类仅有一个实例. Singleton using System; using S ...
- 抽象工厂(Abstract Factory),工厂方法(Factory Method),单例模式(Singleton Pattern)
在谈工厂之前,先阐述一个观点:那就是在实际程序设计中,为了设计灵活的多态代码,代码中尽量不使用new去实例化一个对象,那么不使用new去实例化对象,剩下可用的方法就可以选择使用工厂方法,原型复制等去实 ...
- 十次艳遇单例设计模式(Singleton Pattern)
1.引言 单例设计模式(Singleton Pattern)是最简单且常见的设计模式之一,在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访 ...
- 设计模式 单例模式(Singleton) [ 转载2 ]
设计模式 单例模式(Singleton) [ 转载2 ] @author java_my_life 单例模式的结构 单例模式的特点: 单例类只能有一个实例. 单例类必须自己创建自己的唯一实例. 单例类 ...
- 设计模式 单例模式(Singleton) [ 转载 ]
设计模式 单例模式(Singleton) [ 转载 ] 转载请注明出处:http://cantellow.iteye.com/blog/838473 前言 懒汉:调用时才创建对象 饿汉:类初始化时就创 ...
随机推荐
- iOS开发笔记11:表单键盘遮挡、浮点数价格格式化显示、省市区选择器、View Debugging
1.表单键盘遮挡 应用场景为一个collectionView上有多个textfield.textView供用户填写信息. 之前输入项较少时,采取的方法比较粗暴,didSelectItemAtIndex ...
- 大家一起和snailren学java-(六)复用类
“失恋了,唉,还没开始就结束了……唉……继续看java” 今天又是周末,我们来看看java的复用机制是什么情况.大家知道,代码复用非常实用,这项特性是java的一个重要的部分.那java用什么来实现的 ...
- .net程序员工作两年总结
(2015年9月) 最近换了工作,面试了很多家公司想总结下,以便以后回顾知道自己是怎么走过来的. 入行背景: 我是半路转行做软件开发的,2011年7月大学专科毕业,大学专业是:机械制造及其自动化:20 ...
- setInterval setTimeout clearInterval
setTimeout() 只执行 code 一次.如果要多次调用,请使用 setInterval() 或者让 code 自身再次调用 setTimeout(). //第一次load的时候就先刷新一次 ...
- 优秀的PHP开源项目集合
包管理Package Management Package Management Related 框架 框架组件 微框架Micro Frameworks 内容管理系统Content Managemen ...
- Swift 2.0初探
转眼间,Swift已经一岁多了,这门新鲜.语法时尚.类型安全.执行速度更快的语言已经渐渐的深入广大开发者的心. 今年6月,一年一度的WWDC大会如期而至,在大会上Apple发布了Swift 2.0,引 ...
- Python常见数据结构--列表
列表 Python有6个序列的内置类型,但最常见的是列表和元组. 序列都可以进行的操作包括索引,切片.加.乘.检查成员. 此外,Python已经内置确定序列的长度以及确定最大和最下的元素的方法. ...
- PL/SQL之--流程控制语句
一.简介 像编程语言一样,oracle PL/SQL也有自己的流程控制语句.通过流程控制语句,我们可以在PL/SQL中实现一下比较复杂的业务逻辑操作.而无需到程序中去控制,在一定程度上提高了效率,这也 ...
- JSP过滤器Filter配置过滤类型汇总
一.配置方法1 映射过滤应用程序中所有资源<filter> <filter-name>loggerfilter</filter-name> <fi ...
- Hibernate学习笔记整理系列-------一、Hibernate简介
Hibernate的官网:http://hibernate.org/ 1.1 Hibernate框架的作用 Hibernate框架是一个数据访问框架(也叫持久层框架,可将实体对象变成持久对象).通过H ...