设计模式代表了最佳的实践
引言
- 创建型模式。
- 主要特点:简单,样式多。
- 主要解决:一个全局使用的类频繁地创建与销毁。
- 关键代码:构造函数是私有的。
说明
- 单例只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
优缺点
- 优点:内存里只有一个实例,减少了内存的开销(频繁创建)。
- 缺点:没有接口,不能继承,与单一职责原则冲突。
实现方式
饿汉
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
恶汉模式:没有加锁,执行效率高。但是类加载时就初始化,浪费内存,容易产生垃圾对象。
懒汉
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉模式:第一次调用才初始化,解决了恶汉模式的内存问题。但是每次获取实例都需要同步,加锁影响效率。
双重检查
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;
}
}
双重检查模式
- 第一次判空:在已经实例化的情况下(99.99%的情况),省去了同步操作。
- synchronized:在首次实例化时,保证线程同步。
- 第二次判空:在首次实例化遇到多线程问题时(锁生效的场景),完成实例化后的线程执行不再次进行实例化处理。
- volatile:保证并发场景的有序性。
为什么要保证有序性?
对象创建的执行步骤如下:
-
正常情况
- 1.分配空间
- 2.初始化
- 3.引用赋值
-
重排情况(JIT优化)
- 1.分配空间
- 2.引用赋值
- 3.初始化
在单线程模型的场景下,指令重排不会影响执行结果。JIT指令重排的优化,并不会考虑并发场景。
从上面的重排时序上看,就发现了为什么要使用volatile来保证有序性了。因为引用赋值在前,初始化在后,会导致singleton引用已经不为空了,但是还没有初始化,调用线程会拿到一个没有初始化的引用进行方法调用,会异常奔溃。
ps:如果想查看对象的创建执行步骤,可以使用以下命令:
- 生成字节码文件:javac XX.java;
- 对生成的字节码文件反汇编:javap -c -v XX.class;
- 通过汇编指令以及常量池序号,对应进行查找即可。
静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
静态内部类模式:实现更简单。
枚举
public enum Singleton {
INSTANCE;
public void todo() {
}
}
枚举模式:实现单例模式的最佳方法。更简洁,自动支持序列化机制,防止反序列化。缺点就是可读性差(这玩意儿咋看都不像单例)。
实现方式总结
单例模式实现方案对比
网友评论