设计模式-单例

设计模式-单例

单例介绍

单例的概念

单例模式是一种对象创建模式,它用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例

单例的优点

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销
  • 由于 new 操作的次数减少,因而对系统的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间

单例的六种写法和各自特点

饿汉式

1
2
3
4
5
6
7
8
9
10
public class HungrySingleton {

private static HungrySingleton sInstance = new HungrySingleton();

private HungrySingleton(){}

public static HungrySingleton getInstance(){
return sInstance;
}
}

不足之处:无法对 sInstance 实例做延时加载

优化:懒汉

懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LazySingleton {

private static LazySingleton sInstance;

private LazySingleton(){}

public static LazySingleton getInstance(){
if (sInstance == null) {
sInstance = new LazySingleton();
}
return sInstance;
}
}

不足之处:在多线程并发下这样的实现是无法保证实例是唯一的

优化:懒汉线程安全

懒汉线程安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class LazySafetySingleton {

private static LazySafetySingleton sInstance;

private LazySafetySingleton(){}

//方法中声明 synchronize 关键字
public static synchronized LazySafetySingleton getInstance1(){
if (sInstance == null) {
sInstance = new LazySafetySingleton();
}
return sInstance;
}

//同步代码块实现
public static LazySafetySingleton getInstance(){
synchronized (LazySafetySingleton.class) {
if (sInstance == null) {
sInstance = new LazySafetySingleton();
}
}
return sInstance;
}
}

不足之处:性能

优化:DCL

DCL(双检锁机制)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DCLSingleton {
   
private static volatile DCLSingleton sInstance;
   
private DCLSingleton(){}
   
public static DCLSingleton getInstance(){
//避免不必要的同步
if (sInstance == null) {
synchronized (DCLSingleton.class) {
//在第一次调用时初始化
if (sInstance == null) {
sInstance = new DCLSingleton();
}
}
}
return sInstance;
}
}

不足之处:

  • JVM 的即使编译器中存在指令重排序的优化,解决办法是把 DCLSingleton 变量声明为 volatile

优化:

  • 静态内部类
  • 枚举

指令重排序

  • 在虚拟机层面,为了尽可能减少内存操作速度远慢于 CPU 运行的所带来的 CPU 空置的影响,虚拟机会按照自己的一些规则将程序编写顺序打乱—即写在后面的代码在时间顺序上可能会先执行,而写在前面的代码会后执行—以尽可能充分利用 CPU。
  • 在硬件层面,CPU 会将接收到的一批指令按照其规则重排序,同样是基于 CPU 速度比缓存速度快的原因,和上一点的目的类似,只是硬件处理的话,每次只能在接收到的有限指令范围内重排序,而虚拟机可以在更大层面、更多指令范围内重排序。

静态内部类

1
2
3
4
5
6
7
8
9
10
11
public class StaticInnerSingleton {

public static StaticInnerSingleton getInstance(){
return SingletonHolder.INSTANCE;
}

//静态内部类
private static class SingletonHolder{
private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
}
}

优点:

  • JVM 本身机制保证了线程安全
  • 没有 synchronize 的使用保证了性能优势
  • SingletonHolder 是私有的其他地方没有办法访问

原因:

  • 通过 static 进行区块初始化数据,这样就保证对象在内存是独一份的。因为静态内部类只要没有被使用 JVM 虚拟机就不会去加载,也就不会去加载单例对象,这样就完成了懒汉式加载
  • 使用 final 字段保证字段无法被修改以保证对象的唯一性,所以线程是安全的。

枚举

1
2
3
4
public enum  EnumSingleton {
//定义一个枚举元素,它就是 EnumSingleton 的一个实例
INSTANCE;
}

优点:

  • 写法简单
  • 默认情况下枚举实例的创建时线程安全的,如果要自己添加变量和实例方法一定要注意线程安全

总结:

  • 饿汉:无法对 instance 实例进行延迟加载
  • 懒汉:多线程并发情况下无法保证实例的唯一性
  • 懒汉线程安全:使用 synchronize 导致性能缺陷
  • DCL:JVM 使编译器的指令重排序问题,使用 volatile 关键字
  • 静态内部 / 枚举:延迟加载 / 线程安全 / 性能优势