1 饿汉式
这种方式是最简单的单例模式实现,它在类加载的时候就创建了单例对象。
优点
- 实现简单
- 线程安全
缺点 - 无论是否使用,都会加载单例类并创建单例对象
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
2 懒汉式
2.1 懒汉式(线程不安全)
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2.2 使用同步保证线程安全
优点
- 线程安全
- 延迟加载
缺点 - 效率较低,每次调用
getInstance()
都会同步阻塞
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2.3 懒汉式(双重检查锁定)
这种方式使用双重检查锁定来减少同步开销。
优点
- 线程安全
- 延迟加载
- 效率较高
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
解析
第一步:初次检查 instance 是否为 null
if (instance == null) {
- 初次检查:
- 在进入 synchronized 块之前,先检查 instance 是否为 null。
- 如果 instance 已经被初始化,那么就不需要再次初始化,可以直接返回已存在的实例。
- 这个检查可以避免在每次调用 getInstance() 方法时都进行同步操作,提高了性能。
第二步:同步 Singleton.class 对象
synchronized (Singleton.class) {
2. 同步锁:
- 使用 synchronized 关键字锁定 Singleton.class 对象。
- 这里使用 Singleton.class 作为锁对象是因为它是静态的,并且在整个应用程序生命周期中是唯一的。
- 当一个线程获取到这个锁时,其他试图获取相同锁的线程将被阻塞,直到锁被释放。
- 这样可以确保在任何时候只有一个线程能够进入这个 synchronized 块。
第三步:二次检查 instance 是否为 null
if (instance == null) { instance = new Singleton(); } }
3. 二次检查:
- 再次检查 instance 是否为 null。
- 这一步是为了确保在其他线程已经初始化了 instance 的情况下,当前线程不会重复初始化。
- 这个检查非常重要,因为如果没有这个检查,可能会发生以下问题:
-
- 指令重排序:
-
-
- JVM 或编译器可能会为了优化而重排指令顺序。例如,instance = new Singleton(); 可能会被重排为:
a. 分配内存给 instance。
b. 调用 Singleton 的构造函数初始化 instance。
c. 将 instance 设置为指向新分配的内存地址。
- JVM 或编译器可能会为了优化而重排指令顺序。例如,instance = new Singleton(); 可能会被重排为:
-
-
-
- 如果两个线程几乎同时进入 synchronized 块,其中一个线程完成了第 1 步和第 2 步,但还没有完成第 3 步,这时另一个线程进入 synchronized 块并执行了 new Singleton(),那么第二个线程可能会看到一个尚未完全构造好的 Singleton 实例。
-
-
- 可见性:
-
-
- 如果没有 synchronized 块,那么一个线程创建了 instance 并将其设置为非 null 后,其他线程可能看不到这个更新,因为 Java 内存模型允许线程缓存变量的值。
-
-
-
- synchronized 块保证了 instance 的更新对所有线程都是可见的。
-
3 静态内部类
这种方式利用 Java 的类加载机制来保证初始化实例时只有一个线程。
优点
- 线程安全
- 延迟加载
- 效率较高
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
4 枚举
利用枚举类型的特性来实现单例。
优点
- 线程安全
- 延迟加载
- 防止反射攻击
- 防止序列化攻击
public enum Singleton {
INSTANCE;
public void someMethod() {
// ...
}
}
标签:Singleton,饿汉,private,instance,线程,单例,static,public
From: https://www.cnblogs.com/sherioc/p/18353485