设计模式之——单例模式

前言

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例。我们会理所当然的认为单例模式很简单,其实单例模式是事先方式有很多种,现在就来一一介绍一下。

懒汉式(线程不安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
private static Singleton instance;
/**
* 构造函数私有化
*/
private Singleton() {
}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}

这种写法呢,有懒加载的作用,但是在多线程环境下会线程不安全
为什么会线程不安全呢?
我们假设一下,假如线程A和线程B同时调用getInstance()方法,线程A先执行判断if(instance == null),判断instance对象是空的,这时候线程B获得了CPU执行权,它也判断instance对象是控制,这个时候执行了instance = new Singleton();这段代码,创建了一个对象并把这个对象的引用赋值给了instance,这个时候线程A又获得了执行权,之前已经判断过对象为空了,所以线程A又new了一个新的对象,这个时候就不符合单例模式的要求了,所以这种懒汉式是线程不安全的

懒汉式(线程安全)

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

这个时候,我们加上了synchronized关键字之后,相当于给这个对象加上了锁,就起到了同步的作用,同时只能有一个线程访问这个方法,不过这样的话也降低了效率

饿汉式

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

这种方式基于classloder机制避免了多线程的同步问题,在类初始化的时候就实例化这个对象

静态方法块

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

这种方式和上一种区别不大,都是在类初始化即实例化instance对象

静态内部类

1
2
3
4
5
6
7
8
9
10
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

双重检查锁定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}

双重检查锁定在C语言或者其他语言中已被广泛当做多线程环境下延迟初始化的一种高效手段,但是在Java中并不能很好的实现,这个涉及到Java的内存模型,所以需要加上volatile关键字,这个关键字的作用是保证内存可见性和禁止指令重排序

枚举

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

枚举是JDK1.5之后才有的语法糖,实际上我们创建enum时,编译器会自动为我们生成一个继承自Java.lang.Enum的类
首先,在枚举中明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。
也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。