单例模式

单例Singleton

确保一个类只有一个实例,并提供该实例的全局访问点。Singleton通常被用来代表那些本质上唯一的系统组件,比如窗口管理器或者文件系统

使用一个私有构造函数,一个私有静态变量以及一个公有静态函数来实现。私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。

最简单的单例实现:饿汉模式

1
2
3
4
5
6
7
8
9
10
public class Singleton {
//类装载的时候已经初始化
private static final Singleton instance = new Singleton();
//构造器私有化
private Singleton(){ }
//提供公有的获取方法
public static Singleton getInstance(){
return instance;
}
}

这种实现方式虽然不是最好的实现方式,但是是最常用的单例的实现方式。因为类一开始即被装载,所以不用担心线程安全的问题。但是缺点就是如果不使用这个类,就会存在内存浪费的问题。

线程安全:双重检查模式

加锁操作只需要对实例化部分的代码进行,只有当instance没有被实例化时,才需要进行加锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {
//volatile的申明作用是内存变量共享,和禁止指令重排序
private static volatile Single instance;
//构造器私有化
private Singleton(){}
//提供公有的获取方法
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}

为什么在锁的内部还有再加一层if判断呢,如果只有一个if语句,在instance==null的情况下,如果两个线程都进入了if语句块中,虽然在if语句块中有加锁操作,但两个线程都会执行实例化instance= new Singleton()这条语句,只是时间问题。那么就会进行两次实例化。破坏了单例模式。因此需要两个if语句:第一个语句用来避免instance已经被实例化后的加锁操作,第二个if语句进行了加锁,只有一个线程进入,不会出现多次实例化的情况。

静态内部类实现

当Singleton类被加载时,静态内部类SingletonHolder没有被加载进内存。只有当调用getInstance()方法时从而触发SingletonHolder.instance时SingletonHolder才会被加载。此时初始化INSTANCE实例,并且JVM确保INSTANCE只能被实例化一次。

这种方式不仅具有延迟初始化的好处,而且由JVM提供了对线程安全的支持。

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private Singleton(){}
//静态内部类不会在一开始被装载,所以没有内存消耗的问题
//JVM在装载静态内部类是线程安全的,只有在使用内部类才会去装载,所以线程是安全的
private static class SingletonHolder {
private static final Singleton INSTNCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}

JVM装载内部类并不是程序启动就装载,而且装载内部类是线程安全的。所以这个单例模式真正意义上实现了懒加载与线程安全且节省了内存。

枚举实现

实现单例模式只需编写一个包含单个元素的枚举类型:

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

简洁,且无偿地提供了序列化机制,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候。可以防止反射攻击,防止反序列化重新创建新的对象。

参考资料

[1] Effective Java 中文版

[2] 技术面试必备基础知识

[3] CodeSheep单例模式