单例模式(Singleton)小记
概念
引用维基百科对单例的说明:
单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。
继续引用维基百科的实现思路:
实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
单例的思路就这么简单,不像其他的设计模式那样一般有几个类。它只有一个类。
Java 中的单例实现
懒汉式与饿汉式
在讨论 Java 的单例模式时,猿们一般会提到两者实现方式:懒汉式 和 饿汉式 。为什么会有这两种叫法?看了实现代码后,相信您会忽然大悟。
饿汉式,我是饿汉,不能等,创建(类)时就要给我吃的(实例化对象),不然我会饿死:
public class Singleton{
//创建类时就实例化出单例。
private final static Singleton INSTANCE = new Singleton();
//使用私有的构造器来阻止外部(其他代码)实例化该对象
private Singleton(){}
//由于该类的构造方法是私有的,只能在该类内部实例化对象。因此必须提供一个静态的公有方法作为出口,提供单例。
public static Singleton getInstance(){
return INSTANCE;
}
}
懒汉式, 我是懒汉,你叫我干活(给 INSTANCE new 一个单例)才干,懒是我的天性,这不能怪我:
public class Singleton{
private static Singleton INSTANCE;
private Singleton(){}
public static Singleton getInstance(){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
OK,单例模式就是这么简单,结束了。呵呵,真的结束了吗?图样图森破!
上面的两个实现确实是实现了汉式和懒汉式。而且它们在单线程下能很好地运行。但如果我们把它们应用在多线程下呢?试一下就知道,饿汉式依然坚挺,潇洒应对。但懒汉式就瞬间爆炸了!没办法谁叫你懒,出来混总是要还的。即使是代码,懒也要付出代价~~开玩笑的_,爆炸的原因肯定不是懒的原因啦,是线程同步的问题,造成了非线程安全的懒汉式。
既然上面的懒汉式在多线程下爆炸了,我们就要去拯救它,总不能见死不救吧~~
多线程下懒汉式的自我拯救
知道懒汉式爆炸的原因时线程同步的问题,我们最简单的拯救方法就是直接进行同步加锁,创建线程安全的懒汉式:
public class Singleton{
private static Singleton INSTANCE;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
通过简单的对 getInstance 方法进行加锁就可以把非线程安全的懒汉式改为线程安全的,简单吧!但,正如天下没免费的午餐一样,如此简单的拯救方法肯定是要付出代价的。因此这种实现方法会降低效率。
好吧,效率低下,我改还不行。经过改改改后,我们又得出了另外一种线程安全懒汉式单例:双重校验锁(Double-Checked Locking)
public class Singleton{
private static volatile Singleton INSTANCE;
private Singleton(){}
public static Singleton getInstance(){
if(INSTANCE == null){
synchronized(Singleton.class){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
该方法比之前的线程安全懒汉式效率高的原因是,之前的实现方法每次获取单例是都要进行同步,每一次只能有一个线程进入 getInstance 方法获取实例;而该方法把同步块缩小了,而且只在 INSTANCE 为 null 时才会进行同步访问(也就是说只需要一次同步)。之后都不需要同步,可以并发地获取实例。
很多人都知道,JDK 1.5 之前利用这种方法实现是有问题的。但在 JDK 1.5 后,Java 的内存已经修改了,该方法在 1.5 后能正常运行,可以放心使用。
其他的实现方法
使用静态内部类实现
public class Singleton{
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
public static final Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
该方法利用了静态内部类的特性实现了类似与懒汉式的单例模式
(单元素的)枚举实现
public enum Singleton{
INSTANCE;
}
枚举实现的单例已经简单到不能再简单了,可以直接使用 Singleton.INSTANCE 来获取单例。它虽然简单,但也是线程安全的实现方法,并且也是可序列化的。在 Effective Java 第二版里也推荐使用这种方法: 单元素的枚举类型已经成为实现 Singleton 的最佳方法
单例在 Java 中的应用
- Java.awt.Toolkit with getDefaultToolkit()
- Java.awt.Desktop with getDesktop()
- java.lang.Runtime
java.lang.Runtime 使用的单例(饿汉式,不涉及到线程安全):
public class Runtime{
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime(){
return currentRuntime;
}
private Runtime(){}
//... 其他方法
}
参考
维基百科
StackExchange
使用枚举实现单例
Effective Java 第三条建议