`
阅读更多
看了多方资料,整理下单例设计模式,有不少值得相互探究的地方,你就会发现就这一个小小的单例模式竟然映射出N多知识。我在这里把问题综述出来,一起相互探讨。

单例涉及到的相关文章如下:
                反射、枚举与单例
                序列化与单例
                类加载器与单例

本文则主要是讲多线程与单例。
单例模式首先分为懒汉式和饿汉式。所谓饿汉式即一开始就创建出单例对象,懒汉式则为当需要使用的时候才会去创建出单例对象。

先看下饿汉式:
public final class Singleton {

	private static final Singleton instance=new Singleton();
	
	private Singleton(){}
	
	public static Singleton getInstance(){
		return instance;
	}
}

1 私有化构造器,使得别人无法再创建新对象。
问题1:即使私有化构造器,别人仍然可以通过反射机制来创建新对象,要是这样的话,下面的很多单例方法都不再是单例。然而枚举除外。

2 这种饿汉式的方式在类加载器加载Singleton的时候就会去初始化创建一个Singleton实例,类加载器加载Singleton时线程安全的,所以这种方式不存在线程安全问题。

懒汉式:有时候为了在使用的时候才去创建单例对象,就要采用懒汉式

public final class Singleton {

	private static Singleton instance=null;
	
	private Singleton(){}
	
	public static Singleton getInstance(){
		if(instance==null){
			instance=new Singleton();
		}
		return instance;
	}
}

这种方式即在需要的时候才会去创建单例对象。很明显,大家都知道这种方式引入了线程安全问题,所以要对getInstance方法加上锁,如下:
public final class Singleton {

	private static Singleton instance=null;
	
	private Singleton(){}
	
	public static synchronized Singleton getInstance(){
		if(instance==null){
			instance=new Singleton();
		}
		return instance;
	}
}

这样的话,每个线程要执行getInstance方法时,synchronized对他们进行了同步,保证并发情况下只有一个线程在执行getInstance方法。这种做法的的确解决了线程安全问题,但是却造成了很大的性能开销。因为instance只需要在第一次创建时进行同步,创建后每次获取时不需要再进行同步,所以我们要进一步改进:
public final class Singleton {

	private static Singleton instance=null;
	
	private Singleton(){}
	
	public static Singleton getInstance(){
		if(instance==null){
			synchronized (Singleton.class) {
				instance=new Singleton();
			}
		}
		return instance;
	}
}

这种方式即缩小了同步的范围,保证了在单例对象创建出来后,每次获取时不需要再进行同步,但是又造成了一个问题,即不能保证instance=new Singleton()只被执行一次,所以又要改进,需要在同步的代码中再次检查是否已经创建,如下:
public final class Singleton {

	private static Singleton instance=null;
	
	private Singleton(){}
	
	public static Singleton getInstance(){
		if(instance==null){
			synchronized (Singleton.class) {
				if(instance==null){
					instance=new Singleton();
				}
			}
		}
		return instance;
	}
}

这就是所谓的双重检查机制。看似已经完美,实则不然。instance=new Singleton()实际上分为三个过程:
1 分配内存
2 对Singleton的一些初始化工作包括构造函数的执行
3 对instance变量赋值内存地址
然而对于第2步和第3步,不同的编译器由于执行了优化导致他们的执行顺序并不一致,即发生了重排序,对于重排序参见这篇infoq上的文章http://www.infoq.com/cn/articles/java-memory-model-2
也就是线程1当执行到第2步的时候,instance就已经有值了,此时线程2执行getInstance方法的最外层的if(instance==null)判断就会直接返回。然而该对象还没有真正的完成初始化,还不能正常使用。此时线程2如果去使用该对象,就会出问题了。

为了解决这个问题,就是不允许第2步和第3步进行重排序,使用volatile来解决,如下:

public final class Singleton {

	private volatile static Singleton instance=null;
	
	private Singleton(){}
	
	public static  Singleton getInstance(){
		if(instance==null){
			synchronized (Singleton.class) {
				if(instance==null){
					instance=new Singleton();
				}
			}
		}
		return instance;
	}
}

只需要在Singleton instance变量上加上volatile修饰,就可以禁止重排序。我们知道synchronized 即保证可见性又保证了互斥性,而volatile则仅仅是保持了可见性,而这里volatile又起到禁止重排序的功能(我也不懂,留给大神们去研究)。

另一种解决方案是,基于类初始化的解决方案:

public final class Singleton {

	private static class SingletonHolder{
		public static Singleton instance=new Singleton();
	}
	
	private Singleton(){}
	
	public static  Singleton getInstance(){
		return SingletonHolder.instance;
	}
}

这也是一种常见的懒汉式单例,接下来我们就要分析分析它是如何解决多线程问题的。
当多个线程执行SingletonHolder.instance时,会首先进行类的初始化,即多个线程可能同时去初始化同一个类,这方面对于jvm来说是进行了细致的同步,每个类都有一个初始化锁,来确保只能有一个线程来初始化类。当线程A获取了SingletonHolder类的初始化锁,线程B则需要等待,线程A就要去执行SingletonHolder的静态变量表达式、静态代码块等初始化工作,然后就能确保Singleton instance=new Singleton()只被一个线程来执行。
总的来说,此种方法是依靠jvm对类和接口的同步来实现单例线程安全的。具体jvm对于类和接口初始化的同步过程可以见这篇文章http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization

若想转载请注明出处:   http://lgbolgger.iteye.com/blog/2157820
作者:iteye的乒乓狂魔
分享到:
评论

相关推荐

    【JavaScript源代码】JS实现单例模式的6种方案汇总.docx

    JS实现单例模式的6种方案汇总  今天在复习设计模式中的-创建型模式,发现JS实现单例模式的方案有很多种,稍加总结了一下,列出了如下的6种方式与大家分享 大体上将内容分为了ES5(Function)与ES6(Class)实现两...

    深入浅出java设计模式(高清中文PDF)

    单例模式 3.建造模式 4.原型模式 5.适配器模式 6.桥梁模式 7.组合模式 8.装饰模式 9.门面模式 10.享元模式 11.代理模式 12.责任链模式 13.命令模式 14.解释器模式 15.迭代器模式 16.调停者模式 17....

    设计模式汇总(含思维导图)

    以思维导图的方式,从定义、优缺点、适用场景、角色、UML、代码实现等多个角度去理解设计模式。包括对工厂模式、单例模式、适配器模式、装饰模式、命令模式、观察者模式等常用设计模式的讲述。

    Java常用设计模式例子

    以JAVA为例,汇总了十几种常用的设计模式,包括了:单例模式、工厂模式、建造者模式、适配器模式、装饰器模式、外观模式、命令模式、观察者模式、状态模式、策略模式、模板方法模式等。仅供学习使用。 相关文章请看...

    PHP常用的三种设计模式汇总

    所谓单例模式,即在应用程序中最多只有该类的一个实例存在,一旦创建,就会一直存在于内存中! 单例设计模式常应用于数据库类设计,采用单例模式,只连接一次数据库,防止打开多个数据库连接。 一个

    as3.0设计模式代码实现汇总

    有源码的23种as3 0设计模式案例 单例 工厂 策略 装饰模式等

    java社招二面面试题有哪些

    什么是单例模式 @RequestMapping有什么参数 ConcurrentHashMap怎么实现的,1.7和1.8的区别 执着,怎么解决一个问题 线程的五种状态 sleep和wait的区别 collection和map的区别 集合哪些是排序的 List的Conllections....

    java面试题库2021.pdf

    目录 一、 JavaSE 部分 1、 Java 基础 ①Java 基础部分(基本语法, Java 特性等) ②关键字 ③面向对象 ④集合部分 2、 Java 高级知识 ①线程 ②锁 ...①单例模式 ...4、 所有模式汇总 十、 场景题 十一、 UML

    Think in ActionScript 3.0Ⅰ Ⅱ Ⅲ汇总

    4.7.4 单例模式(Singleton Pattern) 5. 多态(Polymorphism) 5.1 多态的概念 5.2 多态存在的三个必要条件 5.3 TestPolymoph.as —— 多态的应用,体会多态带来的好处 5.4 TestPolymoph 内存分析 5.5 多态的好处 ...

    Java基础知识点总结.docx

    单例设计模式:★★★★★ 156 工厂模式★★★★★ 159 抽象工厂模式★★★★★ 163 建造者模式 170 原型模式 177 适配器模式 182 桥接模式 188 过滤器模式 192 组合模式 193 装饰器模式★★★★★ 196 外观模式 201...

    Android参数传递方法汇总

    参见 http://blog.csdn.net/xcl168 Android开发中,在不同模块(如Activity)间经常会有各种各样的数据需要相互传递,我把... 其中Parcelable接口 Serializable接口 Singleton模式 Bundle方式各有一个代表性的例子。

    项目汇总2

    项目汇总2

    Easy-Programming:漫话编程:通俗易懂的分享基础知识,[数据结构与算法系列],[LeetCode系列],[子系统系列],[设计模式系列],[并发编程系列],[SQL编程] ...合集!同步更新到公众号CVBear以及在线博客http

    单例原型建造者工厂方法抽象工厂 代理适配器装饰装饰外观享元组合 策略命令职能链状态观察者中介者继承器访问者备注模板方法解释器 LeetCode [五杀刷题指南] 致力于通过解决至少同一类算法的5个问题让你掌握算法套路...

    项目汇总1

    不过一般而言,我们使用ajax做动静分离都是都是从服务端请求一个html片段,到了浏览器后,使用dom技术将这个片段整合到页面里.总结:1、使用Thymelea

    python-patterns-study:faifpython-patterns的学习,和其他一些地方来的资料

    这是一个python设计模式及其用法的汇总。 faif / python-patterns。 现有的模式 按常用程度,标记为: :1st_place_medal: , :2nd_place_medal: , :3rd_place_medal: ,空白。 1级最常用/重要/有用,然后常用程度...

    JAVA面向对象详细资料

    32 单例模式 47 32.1 饿汉模式 47 32.2 懒汉模式 47 33 接口(interface) 48 33.1 如何创建一个接口。 48 33.2 如何使用接口 48 33.3 如何使用类实现一个接口 49 33.4 接口的细节 49 34 接口的应用(面向对象分析)...

Global site tag (gtag.js) - Google Analytics