设计模式 单例模式(Singleton) [ 转载 ]
设计模式 单例模式(Singleton) [ 转载 ]
转载请注明出处:http://cantellow.iteye.com/blog/838473
前言
懒汉:调用时才创建对象
饿汉:类初始化时就创建对象
第一种(懒汉,线程不安全):
 public class Singleton {
     private static Singleton instance;
     private Singleton (){}  
     public static Singleton getInstance() {
     if (instance == null) {
         instance = new Singleton();
     }
     return instance;
     }
 }  
这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。
第二种(懒汉,线程安全):
 public class Singleton {
     private static Singleton instance;
     private Singleton (){}
     public static synchronized Singleton getInstance() {
     if (instance == null) {
         instance = new Singleton();
     }
     return instance;
     }
 }  
这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。
第三种(饿汉):
 public class Singleton {
     private static Singleton instance = new Singleton();
     private Singleton (){}
     public static Singleton getInstance() {
     return instance;
     }
 }  
这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
第四种(饿汉,变种):
 public class Singleton {
     private static Singleton instance = null;
     static {
     instance = new Singleton();
     }
     private Singleton (){}
     public static Singleton getInstance() {
     return instance;
     }
 }  
表面上看起来差别挺大,其实更第三种方式差不多,都是在类初始化即实例化instance。
第五种(静态内部类):
 public class Singleton {
     private static class SingletonHolder {
     private static final Singleton INSTANCE = new Singleton();
     }
     private Singleton (){}
     public static final Singleton getInstance() {
     return SingletonHolder.INSTANCE;
     }
 }   
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。
第六种(枚举):
 public enum Singleton {
     INSTANCE;
     public void whateverMethod() {
     }
 }   
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。
第七种(双重校验锁):
 public class Singleton {
     private volatile static Singleton singleton;
     private Singleton (){}
     public static Singleton getSingleton() {
     if (singleton == null) {
         synchronized (Singleton.class) {
         if (singleton == null) {
             singleton = new Singleton();
         }
         }
     }
     return singleton;
     }
 } 
假设没有关键字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方法,指定同一类加载器
private static Class getClass(String classname)
throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if(classLoader == null)
classLoader = Singleton.class.getClassLoader(); return (classLoader.loadClass(classname));
}
}
对第二个问题修复的办法是:在被序列化类中写readResolve方法返回单例引用
 public class Singleton implements java.io.Serializable {
    public static Singleton INSTANCE = new Singleton();     
    protected Singleton() {     
    }
    //将在ObjectInputStream的readObject调用栈中调用
    private Object readResolve() {
             return INSTANCE;
       }
 }   
对我来说,我比较喜欢第三种和第五种方式,简单易懂,而且在JVM层实现了线程安全(如果不是多个类加载器环境),一般的情况下,我会使用第三种方式,只有在要明确实现lazy loading效果时才会使用第五种方式,另外,如果涉及到反序列化创建对象时我会试着使用枚举的方式来实现单例,不过,我一直会保证我的程序是线程安全的,而且我永远不会使用第一种和第二种方式,如果有其他特殊的需求,我可能会使用第七种方式,毕竟,JDK1.5已经没有双重检查锁定的问题了。
========================================================================
不过一般来说,第一种不算单例,第四种和第三种就是一种,如果算的话,第五种也可以分开写了。
所以说,一般单例都是五种写法。懒汉,恶汉,双重校验锁,枚举和静态内部类。
设计模式 单例模式(Singleton) [ 转载 ]的更多相关文章
- 设计模式 单例模式(Singleton) [ 转载2 ]
		
设计模式 单例模式(Singleton) [ 转载2 ] @author java_my_life 单例模式的结构 单例模式的特点: 单例类只能有一个实例. 单例类必须自己创建自己的唯一实例. 单例类 ...
 - JAVA设计模式-单例模式(Singleton)线程安全与效率
		
一,前言 单例模式详细大家都已经非常熟悉了,在文章单例模式的八种写法比较中,对单例模式的概念以及使用场景都做了很不错的说明.请在阅读本文之前,阅读一下这篇文章,因为本文就是按照这篇文章中的八种单例模式 ...
 - 浅谈设计模式--单例模式(Singleton Pattern)
		
题外话:好久没写blog,做知识归纳整理了.本来设计模式就是个坑,各种文章也写烂了.不过,不是自己写的东西,缺少点知识的存在感.目前还没做到光看即能记住,得写.所以准备跳入设计模式这个大坑. 开篇先贡 ...
 - [工作中的设计模式]单例模式singleton
		
一.模式解析: 单例模式是最简单和最常用的设计模式,面试的时候,不管新毕业的学生还是已经工作多年的筒子,对单例模式基本都能聊上两句.单例模式主要体现在如下方面: 1.类的构造函数私有化,保证外部不能直 ...
 - 23种设计模式--单例模式-Singleton
		
一.单例模式的介绍 单例模式简单说就是掌握系统的至高点,在程序中只实例化一次,这样就是单例模式,在系统比如说你是该系统的登录的第多少人,还有数据库的连接池等地方会使用,单例模式是最简单,最常用的模式之 ...
 - 设计模式--单例模式Singleton(创建型)
		
单例模式很显然是定义一个类,这个类在程序中只有唯一的实例对象.一般单例类的构造函数是私有的,只能通过调用静态函数GetInstance来获取实例. 一.单例模式有三种:懒汉式单例.饿汉式单例.登记式单 ...
 - 设计模式--单例模式Singleton
		
单例模式顾名思义整个程序下只有一个实例,例如一个国家只有一个皇帝,一个军队只有一个将军.单例模式的书写又分为饿汉模式和懒汉模式 饿汉模式 类中代码 package demo; public cla ...
 - 设计模式——单例模式(Singleton)
		
保证一个类仅有一个实例,并提供一个访问它的全局访问点.——DP UML类图 模式说明 个人认为单例模式是所有设计模式中最为简单的一个模式,因为实现这个模式仅需一个类,而不像其他模式需要若干个类.这个模 ...
 - 设计模式-单例模式(Singleton) (创建型模式)
		
//以下代码来源: 设计模式精解-GoF 23种设计模式解析附C++实现源码 //Singleton.h #pragma once #include<iostream> class Sin ...
 
随机推荐
- 数据库开启gtid时,需要注意的问题
			
1.slave不能执行任何sql,包括超级用户2.read_only=on,这个必须要开启,避免业务执行sql3.保证当前slave的事务id为1 当slave同步出现问题时,手动跳过,需要考虑的问题 ...
 - IOS GCD使用实例大全
			
GCD是大家在IOS开发过程中经常使用的一种多线程管理机制.原理这里就不多说了,大家关心的大部分都是它的使用,下面主要介绍GCD的主要方法及其实例. 1.认识主队列,感受串行队列的运行,运行结果打印的 ...
 - childNodes属性 和 nodeType属性
			
childNodes属性可以用来获取任何一个元素的所有子元素,它是一个包含这个元素的全部子元素的数组:element.childNodes 如果需要把某个文档的body元素的全体子元素检索出来.首先使 ...
 - sort vector - leetcode 新用法
			
179. Largest Number sort(num.begin(), num.end(), [](int a, int b){ return to_string(a)+to_string(b) ...
 - 从A页面跳转到B页面,从B页面按浏览器自带按钮返回到A页面并且刷新页面--手机操作浏览器自带返回并自带刷新
			
<html> <head> <meta http-equiv="Content-Type" content="text/html; char ...
 - CODE[VS]-保留两位小数-浮点数处理-天梯青铜
			
题目描述 Description 保留两位小数输出一个浮点数. 输入描述 Input Description 一个浮点数.double范围内 输出描述 Output Description 保留两位小 ...
 - UIColor,CGColor,CIColor三者间的区别和联系
			
一.UIColor UIColor是UIKit中存储颜色信息的一个重要的类,一个UIColor对象包含了颜色和透明度的值,它的颜色空间已经针对IOS进行了优化.UIColor包含了一些类方法用于创建一 ...
 - vimtutor基础教程
			
第一讲: 1. 光标在屏幕文本中的移动既可以用箭头键,也可以使用 hjkl 字母键. h (左移) j (下行) k (上行) l (右移) 2. 欲 ...
 - iOS根据网络图片的size大小设置UIImageView的大小
			
有时候在设置UIImageView的大小时候需要根据UIimage的长宽比来自动设置,不让图片原比例失真. 如果是从本地获取到的图片,[UIImage imageNamed:@"" ...
 - 第一百一十九节,JavaScript事件入门
			
JavaScript事件入门 学习要点: 1.事件介绍 2.内联模型 3.脚本模型 4.事件处理函数 JavaScript事件是由访问Web页面的用户引起的一系列操作,例如:用户点击.当用户执行某些操 ...