设计模式代表了最佳的实践
引言
-
创建型模式。
-
主要特点:简单,样式多。
-
主要解决:一个全局使用的类频繁地创建与销毁。
-
关键代码:构造函数是私有的。
说明
-
单例只能有一个实例。
-
单例类必须自己创建自己的唯一实例。
-
单例类必须给所有其他对象提供这一实例。
优缺点
-
优点:内存里只有一个实例,减少了内存的开销(频繁创建)。
-
缺点:没有接口,不能继承,与单一职责原则冲突。
实现方式
饿汉
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:保证并发场景的有序性。
为什么要保证有序性?
对象创建的执行步骤如下:
在单线程模型的场景下,指令重排不会影响执行结果。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() {
}
}
枚举模式
:实现单例模式的最佳方法。更简洁,自动支持序列化机制,防止反序列化。缺点就是可读性差(这玩意儿咋看都不像单例)。
实现方式总结
小编的博客系列
设计模式 全家桶