1. 单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
1.1 饿汉实现
这种方式比较简单,但在高性能程序上很少会这样使用。
- 优点:没有加锁,执行效率会提高。
- 缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,导致资源浪费、延迟了启动程序时间。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
1.2 懒汉实现
-
不安全的方式
- 由于多线程环境,该方式是一个非常不安全的方法,会导致创建多个对象。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 效率很低,每个线程在获取单例的时候都需要竞争锁,导致资源浪费和效率低下。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 双重检查与Volatile。
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
**问题来了,为什么需要volatile来修饰单例对象**
这是因为操作系统和java虚拟机会对程序进行优化,导致原本顺序执行的代码,在操作系统和虚拟机认为是安全的情况下,进行优化,导致代码乱序执行。在通常情况下,这种“优化”对于程序是没有影响的,volatile可以保证操作系统对于singleton对象的读写过程不允许乱序执行。其核心原理是通过在volatile变量读写时使用内存屏障。
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;
}
}
举例来说,有A、B两个线程,A在执行到这部分时,内存已经创建好了对象,但是singleton执行内存中的对象还没有完全实例化。当A释放锁后,B线程判断singleton是否为null?不为null,于是B线程直接返回了singleton对象,此时该对象还没有完全实例化。
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
- 静态内部类
Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。
因此,这种方式相比DCL方式显得更合理。
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 whateverMethod() {
}
}
总结
由于编译器优化等原因,我们的程序代码会存在乱序执行,以至于对象在发布时是半初始化的,为保证懒汉模式中数据的导出是安全的,需要增加volatile修饰。
多线程系列在github上有一个开源项目,主要是本系列博客的实验代码。
https://github.com/forestnlp/concurrentlab
如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。