是什么
Ensure a class only has one instance, and provide a global point of access to it.
确保一个类只有一个实例,并且提供一个全局的方法来访问这个类。
单例模式的实现
- 饿汉式
- 懒汉式
- 静态内部类
- 枚举
优缺点
优点:
全局提供一个类的实例,防止一个类被重复实例。
缺点
类内的全局变量的使用涉及到非线程安全。
饿汉式
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;
}
}
在类的加载的时候就已经进行了初始化,所以是线程安全的,但是类的加载会影响程序的启动时间。
懒汉式
非线程安全
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
//私有构造函数
private Singleton() {}
//单例对象
private static Singleton instance = null;
//静态工厂方法
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
当多个进程同时来调用该方法时:假如A,B线程同时来到了 if 判断语句这里,同时判断为 true ,那么就获得了2个实例了。
当想要获取对应的单例时,再实例化该类,从而不再影响程序的启动。
线程安全
双重检验锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Singleton {
//私有构造函数
private Singleton() {}
//单例对象,使用 volatile 关键字禁止指令重排序
private volatile static Singleton instance = null;
//静态工厂方法
public static Singleton getInstance() {
//双重检测机制
if (instance == null) {
//同步锁
synchronized (Singleton.class){
//双重检测机制
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
注意在声明 单例 的时候,使用 volatile 关键字禁止指令重排序。
静态内部类
1
2
3
4
5
6
7
8
9
10
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
第一次加载 Singleton 类时并不会初始化 instance ,只有第一次调用 getInstance() 方法时虚拟机加载 SingletonHolder 并初始化instance ,这样不仅能确保线程安全也能保证 Singleton 类的唯一性。
枚举
1
2
3
4
5
public enum Singleton {
INSTANCE;
public void doSomeThing() {
}
}
枚举声明的单例是线程安全的,在程序启动的时候就会加载该类。 枚举 声明的类不会因为反序列化或者反射就能获得第二个该类的实例,但是其他的几种模式会。
反射打破单例
1
2
3
4
5
6
7
8
9
//获得构造器
Constructor con = Singleton.class.getDeclaredConstructor();
//设置为可访问
con.setAccessible(true);
//构造两个不同的对象
Singleton singleton1 = (Singleton)con.newInstance();
Singleton singleton2 = (Singleton)con.newInstance();
//验证是否是不同对象
System.out.println(singleton1 == singleton2);
注意
- 使用枚举实现的单例模式,不但可以防止利用反射强行构建单例对象,而且可以在枚举类对象被反序列化的时候,保证反序列的返回结果是同一对象。
- 对于其他方式实现的单例模式,如果既想要做到可序列化,又想要反序列化为同一对象,则必须实现 readResolve 方法。
单例模式 | 是否线程安全 | 是否懒加载 | 是否防止反射,反序列化构建 |
---|---|---|---|
饿汉式 | 是 | 否 | 否 |
双重检验锁 | 是 | 是 | 否 |
静态内部类 | 是 | 是 | 否 |
枚举 | 是 | 否 | 是 |
java中的应用
- java.lang.Runtime#getRuntime()
- java.awt.Desktop#getDesktop()
- java.lang.System#getSecurityManager()
参考
- https://github.com/iluwatar/java-design-patterns/tree/master/singleton
- https://zhuanlan.zhihu.com/p/33102022