本文是对面向对象设计模式--单例模式(Singleton)的解析,主要分为定义解析、经典单例模式、多线程环境下的单例模式、多案例练习加深对单例模式的理解、最后总结知识要点。
第一篇:定义解析
单例模式是GoF四人帮整理的《设计模式-可复用面向对象软件基础》一书中23种设计模式中归类为创建型模式中的设计模式,23种设计模式根据它们的用途分为三大类:5种创建型模式、7种结构型模式、11种行为型模式。
引自GoF四人帮对单例模式(Singleton)的定义与类图:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
保证一个类仅有一个实例并提供一个访问它的全局访问点,通过定义可以清晰的知道单例模式的目的,保证一个类在系统中只能有一个实例对象,这意味着不能让任何其它类可以直接通过new来实例化一个新的实例对象。提供一个访问它的全局访问点,即提供一个获取单例对象的方法,可以在系统其它地方通过全局访问点获取这个单例对象。
第二篇:经典的单例模式
单例模式用来创建独一无二的,只能有一个实例的对象。有一些对象在系统中只需要一个,如线程池、缓存和注册表等,这些类对象只能有一个实例,如果制造出多个实例,就会导致许多问题发生,如程序的行为异常、不一致的结果等。
为了防止类对象被多次创建,单例对象的构建方法必须是私有的。而且需要有一个静态方法,通过类来调用这个方法获取实例对象。
单例对象通常用来管理共享的资源。
经典的单例模式实现:
public class Singleton{
private static Singleton uniqueInstance;
private Singleton(){}
public static Stingleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
1)通过一个静态变量uniqueInstance来记录Singleton的惟一实例。
2)把构建器声明为私有,只有在Singleton内部才能调用。
3)通过全局访问方法getInstance()实例化对象,并返回对象实例。
第三篇:多线程环境下的单例模式
如图所示,在多线程的情况下经典的单例模式实现会出现问题,创建了多个实例对象。
在多线程环境下,保证单例类只有一个对象方法:
1)使用synchronize将getInstance()变成同步方法
public class Singleton{
private static Singleton uniqueInstance;
private Singleton(){}
public static synchronize Stingleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
使用synchronize将方法变成同步方法,保证每次只有一个线程执行getInstance()方法,这样就保证了只会有一次new实例化对象,从而保证了多线程环境下,只会创建一个对象。但是通过分析,其实我们只有第一次调用getInstance()方法的时候才需要同步,一旦实例化对象之后就不需要再同步,但使用此方法,每一次调用都会进行同步,当频繁调用的时候,会严重拖累系统的性能。
2)使用静态初始化器创建单例对象,也称急切的单例模式。
public class Singleton{
private static Singleton uniqueInstance = new Singleton();
private Singleton(){}
public static Stingleton getInstance(){
return uniqueInstance;
}
}
通过静态初始化器创建单例对象,保证了线程案例。利用这种做法,我们依赖JVM加载这个类时马上创建惟一实例对象。JVM保证任何线程在访问uniqueInstance对象之前一定先创建此实例。此种方法也种急切的单例模式,在系统加载类时便创建单例对象,会增加初始化时资源消耗。
注意:如果存在两个类加载器以上的情况,每个类加载器可能会创建各自的单例。每个类加载器都定义一个命名空间,如果有两个以上的类加载器,不同的类加载器可能会加载同一个类,从而从程序上看一个类被加载了多次,解决办法:自行指定类加载器,并指定同一个类加载器。JVM1.2之前垃圾回收器可能会吃掉单例对象,需要建立单例注册表修复。
3)使用双重检查加锁的方式
public class Singleton{
private static volatile Singleton uniqueInstance;
private Singleton(){}
public static Stingleton getInstance(){
if(uniqueInstance == null){
synchronize(Singleton.class){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
利用双重检查加锁的方式,只有第一次调用getInstance()创建单例对象时会进行同步,从而保证多线程环境下,只创建一个实例对象。当单例对象创建完成后,再调用getInstance()方法将不会进行同步,采用此方法,提高了性能,大大减少了getInstance()的损耗。
uniqueInstance使用volatile声明,保证单例对象引用在多线程环境下对各线程都是可见的,一旦创建单例对象完成,所有线程都将知晓对象已创建,uniqueInstance不再为null,从而保证双重检查加锁的方式能够正确保证只会创建一个对象。
注意:JDK1.5之前的版本,因为volatile的实现会导致双重加锁失效。故不能采用此实现。
第四篇:案例实践
练习案例源码:https://github.com/chentian114/100-dayaction/tree/master/designpattern-1
ClassicSingletonDemo经典的单例模式案例:
在多线程环境下会出错。
StaticSingletonDemo急切的单例模式案例:
在静态加载类时创建对象
SynchronizedSingletonDemo同步的单例模式案例:
直接在获取单例的方法上通过synchronized同步来获取单例,在大量调用的情况下,性能会越来越差。
ThreadSingletonDemo双重检查加锁的方式实现单例案例:
通过使用双重检查与volatile与局部使用synchronized,在实现线程安全地创建单例情况下不会对性能有太大的影响。只有第一次创建单例的时间需要同步。被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此一般,没有特别需要,不要使用。
EnumSingletonDemo使用枚举类型实现单例模式案例:
使用枚举实现单例非常简单,只需要编写一个包含单个元素的枚举类型即可。使用枚举来实现单例控制会更加简洁,而且提供了序列化机制,并由JVM提供保障防止多次实例化。
第五篇:总结
单例模式确保程序中一个类最多只有一个实例对象。单例模式提供了访问这个实例的全局访问点。实现单例模式一般需要私有化构建方法,一个静态变量和一个静态方法。
单例模式的优点:1)减少内存开支2)减少创建对象所需系统的性能开销3)避免资源的多重占用。
单例模式的缺点:1)扩展困难,只能直接对代码进行修改。
单例模式的使用场景:1)要求生成唯一序列号的环境2)整个项目中只需要一个共享访问点3)创建一个对象需要消耗的资源过多。