设计模式 - 单例模式
单例模式
单例模式是指在应用运行过程中保证某一个类仅有一个实例,并提供一个获取该实例的方法,是一种创建对象的方式。
为什么使用单例模式
节省时间
由于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级的对象而言,是很重要的。
节约资源
不需要频繁创建对象,同时也减轻了 GC 压力
实现单例模式注意的点
- 单例类只能有一个实例
- 单例类的构造方法必须是私有的
- 单例类必须提供一个静态方法给外部获取该类的唯一实例
实现单例模式的几种方法
懒汉式
在调用 getInstance 方法获取实例的时候才实例化单例类,在多线程环境中容易产生多个实例,因为这种写法并不是线程安全的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
饿汉式
饿汉式的实现在类初始化的时候就创建该类的实例了,即使没有调用 getInstance() 方法。所以饿汉的缺点就是它并不是一种懒加载的方式。
1
2
3
4
5
6
7
8
9
10
11
12
public class HungrySingleton {
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
单次检查懒汉式
在创建单例类的实例之前用 synchronized 关键字同步该类,确保此时只有一个线程能执行创建实例的代码,但是如果多个线程同时调用 getInstance() 方法,同时判断 lazySingleton == null 那么就会进入 if 代码块此时 A 线程得到 CPU 的控制权进入同步代码块然后创建对象、返回对象,线程 A 完成之后,接着 B 线程得到 CPU 的控制权同样能进入同步代码块创建对象、返回对象。这种情况下返回的两个对象是不同的,所以并不是单例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SingleCheckLazySingleton {
private static SingleCheckLazySingleton lazySingleton = null;
private SingleCheckLazySingleton() {
}
public static SingleCheckLazySingleton getInstance() {
if (lazySingleton == null) {
synchronized (DoubleCheckLazySingleton.class) {
lazySingleton = new SingleCheckLazySingleton();
}
}
return lazySingleton;
}
}
双重检查懒汉式
双重检查懒汉式就是为了解决上一种方式还存在的问题的,在同步代码块内部再进行一次空判断,如果多个线程同时进入 if 代码块,当有一个线程完成了实例化操作之后,第二个线程在进行实例化操作之前会先检查单例对象是否已经创建,如果已经创建则返回该实例,这样就避免了创建多个实例。
另外,由于 new DoubleCheckLazySingleton() 并不是一个原子操作,使用 volatile 修饰实例变量可以确保指令不会被重排。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DoubleCheckLazySingleton {
private volatile static DoubleCheckLazySingleton doubleCheckLazySingleton = null;
private DoubleCheckLazySingleton() {
}
public static DoubleCheckLazySingleton getInstance() {
if (doubleCheckLazySingleton == null) {
synchronized (DoubleCheckLazySingleton.class) {
if (doubleCheckLazySingleton == null) {
doubleCheckLazySingleton = new DoubleCheckLazySingleton();
}
}
}
return doubleCheckLazySingleton;
}
}
防止指令重排序: 防止new DoubleCheckLazySingleton() 时指令重排序导致其他线程获取到未初始化完的对象。doubleCheckLazySingleton = new DoubleCheckLazySingleton(); 这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。1.给 instance 分配内存;2.调用 Singleton 的构造函数来初始化成员变量;3.将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)。但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后报错。
静态内部类
静态内部类也能实现单例模式的懒加载,因为内部静态类是要在有引用了以后才会装载到内存的,所以在第一次调用 getInstance() 之前,Holder 是没有被装载进来的,只有在第一次调用了 getInstance() 之后,里面涉及到了return,return Holder.instance;
才产生了对 Holder 的引用,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {
}
public static StaticInnerClassSingleton getInstance() {
return Holder.STATICINNERCLASSSINGLETON;
}
private static class Holder {
private static StaticInnerClassSingleton STATICINNERCLASSSINGLETON = new StaticInnerClassSingleton();
}
}
枚举
枚举天然就是单例,是写法最简单的一种
1
2
3
4
public enum EnumSingleton {
ENUM_SINGLETON
}
总结
实现方式 | 是否线程安全 | 是否懒加载 |
---|---|---|
懒汉式 | 否 | 是 |
饿汉式 | 是 | 否 |
单次检查懒汉式 | 否 | 是 |
双重检查懒汉式 | 是 | 是 |
静态内部类 | 是 | 是 |
枚举 | 是 | 否 |