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

转载请注明出处:http://cantellow.iteye.com/blog/838473

前言

懒汉:调用时才创建对象

饿汉:类初始化时就创建对象

第一种(懒汉,线程不安全):

  1. public class Singleton {
  2. private static Singleton instance;
  3. private Singleton (){}
  4.  
  5. public static Singleton getInstance() {
  6. if (instance == null) {
  7. instance = new Singleton();
  8. }
  9. return instance;
  10. }
  11. }

这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。

第二种(懒汉,线程安全):

  1. public class Singleton {
  2. private static Singleton instance;
  3. private Singleton (){}
  4. public static synchronized Singleton getInstance() {
  5. if (instance == null) {
  6. instance = new Singleton();
  7. }
  8. return instance;
  9. }
  10. }

这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。

第三种(饿汉):

  1. public class Singleton {
  2. private static Singleton instance = new Singleton();
  3. private Singleton (){}
  4. public static Singleton getInstance() {
  5. return instance;
  6. }
  7. }

这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

第四种(饿汉,变种):

  1. public class Singleton {
  2. private static Singleton instance = null;
  3. static {
  4. instance = new Singleton();
  5. }
  6. private Singleton (){}
  7. public static Singleton getInstance() {
  8. return instance;
  9. }
  10. }

表面上看起来差别挺大,其实更第三种方式差不多,都是在类初始化即实例化instance。

第五种(静态内部类):

  1. public class Singleton {
  2. private static class SingletonHolder {
  3. private static final Singleton INSTANCE = new Singleton();
  4. }
  5. private Singleton (){}
  6. public static final Singleton getInstance() {
  7. return SingletonHolder.INSTANCE;
  8. }
  9. }  

这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。

第六种(枚举):

  1. public enum Singleton {
  2. INSTANCE;
  3. public void whateverMethod() {
  4. }
  5. }  

这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。

第七种(双重校验锁):

  1. public class Singleton {
  2. private volatile static Singleton singleton;
  3. private Singleton (){}
  4. public static Singleton getSingleton() {
  5. if (singleton == null) {
  6. synchronized (Singleton.class) {
  7. if (singleton == null) {
  8. singleton = new Singleton();
  9. }
  10. }
  11. }
  12. return singleton;
  13. }
  14. }

  假设没有关键字volatile的情况下,两个线程A、B,都是第一次调用该单例方法,线程A先执行instance = new Instance(),该构造方法是一个非原子操作,编译后生成多条字节码指令,由于JAVA的指令重排序,可能会先执行instance的赋值操作,该操作实际只是在内存中开辟一片存储对象的区域后直接返回内存的引用,之后instance便不为空了,但是实际的初始化操作却还没有执行,如果就在此时线程B进入,就会看到一个不为空的但是不完整(没有完成初始化)的Instance对象,所以需要加入volatile关键字,禁止指令重排序优化,从而安全的实现单例。

为何要使用双重检查锁定呢?

考虑这样一种情况,就是有两个线程同时到达,即同时调用 GetInstance(),

此时由于 singleton == null ,所以很明显,两个线程都可以通过第一重的 singleton == null ,

进入第一重 if 语句后,由于存在锁机制,所以会有一个线程进入 lock 语句并进入第二重 singleton == null ,

而另外的一个线程则会在 lock 语句的外面等待。

而当第一个线程执行完 new  Singleton()语句后,便会退出锁定区域,此时,第二个线程便可以进入 lock 语句块,

此时,如果没有第二重 singleton == null 的话,那么第二个线程还是可以调用 new  Singleton()语句,

这样第二个线程也会创建一个 Singleton 实例,这样也还是违背了单例模式的初衷的,

所以这里必须要使用双重检查锁定。

细心的朋友一定会发现,如果我去掉第一重 singleton == null ,程序还是可以在多线程下完好的运行的,

考虑在没有第一重 singleton == null 的情况下,

当有两个线程同时到达,此时,由于 lock 机制的存在,第一个线程会进入 lock 语句块,并且可以顺利执行 new Singleton(),

当第一个线程退出 lock 语句块时, singleton 这个静态变量已不为 null 了,所以当第二个线程进入 lock 时,

还是会被第二重 singleton == null 挡在外面,而无法执行 new Singleton(),

所以在没有第一重 singleton == null 的情况下,也是可以实现单例模式的?

那么为什么需要第一重 singleton == null 呢?

这里就涉及一个性能问题了,因为对于单例模式的话,new Singleton()只需要执行一次就 OK 了,

而如果没有第一重 singleton == null 的话,每一次有线程进入 GetInstance()时,均会执行锁定操作来实现线程同步,

这是非常耗费性能的,而如果我加上第一重 singleton == null 的话,

那么就只有在第一次,也就是 singleton ==null 成立时的情况下执行一次锁定以实现线程同步,

而以后的话,便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock 语句块了,这样就可以解决由线程同步带来的性能问题了。

  

这个是第二种方式的升级版,俗称双重检查锁定,详细介绍请查看:http://www.ibm.com/developerworks/cn/java/j-dcl.html

在JDK1.5之后,双重检查锁定才能够正常达到单例效果。

总结

有两个问题需要注意:

1.不同类装载器

如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。

2.反序列化

如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例

对第一个问题修复的办法是:重写被序列化类的getClass方法,指定同一类加载器

  1. private static Class getClass(String classname)
  2. throws ClassNotFoundException {
  3. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  4.  
  5. if(classLoader == null)
  6. classLoader = Singleton.class.getClassLoader();
  7.  
  8. return (classLoader.loadClass(classname));
  9. }
  10. }

 对第二个问题修复的办法是:在被序列化类中写readResolve方法返回单例引用

  1. public class Singleton implements java.io.Serializable {
  2. public static Singleton INSTANCE = new Singleton();
  3.  
  4. protected Singleton() {
  5.  
  6. }
  7.  
  8. //将在ObjectInputStream的readObject调用栈中调用
  9. private Object readResolve() {
  10. return INSTANCE;
  11. }
  12. }

  对我来说,我比较喜欢第三种第五种方式,简单易懂,而且在JVM层实现了线程安全(如果不是多个类加载器环境),一般的情况下,我会使用第三种方式,只有在要明确实现lazy loading效果时才会使用第五种方式,另外,如果涉及到反序列化创建对象时我会试着使用枚举的方式来实现单例,不过,我一直会保证我的程序是线程安全的,而且我永远不会使用第一种和第二种方式,如果有其他特殊的需求,我可能会使用第七种方式,毕竟,JDK1.5已经没有双重检查锁定的问题了。

========================================================================

  不过一般来说,第一种不算单例,第四种和第三种就是一种,如果算的话,第五种也可以分开写了。

  所以说,一般单例都是五种写法。懒汉,恶汉,双重校验锁,枚举和静态内部类。

设计模式 单例模式(Singleton) [ 转载 ]的更多相关文章

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

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

  2. JAVA设计模式-单例模式(Singleton)线程安全与效率

    一,前言 单例模式详细大家都已经非常熟悉了,在文章单例模式的八种写法比较中,对单例模式的概念以及使用场景都做了很不错的说明.请在阅读本文之前,阅读一下这篇文章,因为本文就是按照这篇文章中的八种单例模式 ...

  3. 浅谈设计模式--单例模式(Singleton Pattern)

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

  4. [工作中的设计模式]单例模式singleton

    一.模式解析: 单例模式是最简单和最常用的设计模式,面试的时候,不管新毕业的学生还是已经工作多年的筒子,对单例模式基本都能聊上两句.单例模式主要体现在如下方面: 1.类的构造函数私有化,保证外部不能直 ...

  5. 23种设计模式--单例模式-Singleton

    一.单例模式的介绍 单例模式简单说就是掌握系统的至高点,在程序中只实例化一次,这样就是单例模式,在系统比如说你是该系统的登录的第多少人,还有数据库的连接池等地方会使用,单例模式是最简单,最常用的模式之 ...

  6. 设计模式--单例模式Singleton(创建型)

    单例模式很显然是定义一个类,这个类在程序中只有唯一的实例对象.一般单例类的构造函数是私有的,只能通过调用静态函数GetInstance来获取实例. 一.单例模式有三种:懒汉式单例.饿汉式单例.登记式单 ...

  7. 设计模式--单例模式Singleton

    单例模式顾名思义整个程序下只有一个实例,例如一个国家只有一个皇帝,一个军队只有一个将军.单例模式的书写又分为饿汉模式和懒汉模式 饿汉模式   类中代码 package demo; public cla ...

  8. 设计模式——单例模式(Singleton)

    保证一个类仅有一个实例,并提供一个访问它的全局访问点.——DP UML类图 模式说明 个人认为单例模式是所有设计模式中最为简单的一个模式,因为实现这个模式仅需一个类,而不像其他模式需要若干个类.这个模 ...

  9. 设计模式-单例模式(Singleton) (创建型模式)

    //以下代码来源: 设计模式精解-GoF 23种设计模式解析附C++实现源码 //Singleton.h #pragma once #include<iostream> class Sin ...

随机推荐

  1. Spring MVC 返回json数据 报406错误 问题解决方案

    将jackson jar包改为jackson-databind-2.5.0.jar  jackson-core-2.5.0.jar  jackson-annotations-2.5.0.jar(这个版 ...

  2. github从上传代码到发布静态网站

    1.上传代码 第一步:建立git仓库 cd到你的本地项目根目录下,执行git命令 git init 第二步:将项目的所有文件添加到仓库中 git add . 如果想添加某个特定的文件,只需把.换成特定 ...

  3. mysql数据库主从搭建

    一.最近一直在学习mysql的东西,刚好看到mysql如何搭建主从数据库,搜集了很多资料后大致了解了mysql主从复置的原理.以下是我的理解: 举例master为主数据库,slave为从数据库. sl ...

  4. vue 2.0 无法编译ES6语法

    # vue2.0 webpack 无法编译 ES6 语法 之前在使用 vue 1.x 时用 vue-loader@8.0.0 版本可以正常打包vue的代码,包括ES6语法也能正常转为ES5语法,但是当 ...

  5. 我理解的session和cookie

    tomcat管理session 理解tomcat是如何生成session的. 当用户请求到web资源有request.getSession()这段代码的时候,request首先会对请求的url判读是否 ...

  6. QT学习笔记—1

    1.模态和非模态的区别:非模态可以同时操作两个窗口,模态的只能在顶层窗口关闭之后才能使用其他窗口 //同时显示出widget和dialog窗口,非模态     QDialog *dialog = ne ...

  7. AndroidStudio0.5.2 BUG 导致 menu 菜单键崩溃

    郁闷了半天,今天发现一点击手机 menu 键应用就崩溃了,记得之前都是好好的,调试了半天代码还是搞不定,于是网上google了一番,发现仅国外有一两篇文章有提到类似问题,据说是 0.5.2 版本的 B ...

  8. mmmmmmmm

    // // AView.m // AutoLayout // // Created by ZhuYi on 16/5/24. // Copyright © 2016年 ZY. All rights r ...

  9. [Jmeter]jmeter之参数化

    一.同一个服务器不同界面访问 a 准备工作: 1.启动jmeter: 2.创建需要访问的url文件,内容示例如下: 即比如:http://www.cnblogs.com/amberly/p/59651 ...

  10. cocos坐标系及坐标转换

    cocos2dx的坐标系是以左下角为(0,0)点, 向右上为正 世界坐标系:相对于屏幕位置的坐标表示 节点坐标系:相对于父节点的坐标表示 可以相互转换: /** * 将世界坐标转换成节点坐标,忽略锚点 ...