// 饿汉模式
public final class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
-
使用了
static
修饰了成员变量instance,所以该变量会在 类初始化
的过程中被收集进
类构造器 <clinit>
-
在 多线程
场景下, JVM
会保证只有一个线程能够执行该类的
<clinit>
方法,其它线程将会被 阻塞等待
-
等到唯一的一次
<clinit>
方法执行完成后,其它线程将不会再执行 <clinit>
方法,转而执行自己的代码
-
因此,static修饰的成员变量instance,在 多线程
的情况下能保证 只实例化一次
-
在 类初始化
阶段就已经在堆内存中开辟了一块内存,用于存放实例化对象,所以也称为 饿汉模式
-
可以保证 多线程
情况下实例的唯一性,而且getInstance直接返回唯一实例, 性能很高
-
在类成员变量比较多,或者变量比较大的情况下,这种模式可能会在没有使用类对象的情况下, 一直占用堆内存
- 第三方框架一般不会采用饿汉模式来实现单例模式
懒汉模式
Double-Check
// 懒汉模式
public final class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
-
懒汉模式是为了 避免加载类对象时提前创建对象
的一种单例模式
-
懒汉模式使用了 懒加载
方式,只有当系统使用到类对象时,才会将实例加载到堆内存中
-
上面的代码在 多线程
环境下,可能会出现 实例化多个类对象
的情况
- 当线程A进入到if判断条件后,开始实例化对象,此时instance依然为null
- 又有线程B进入到if判断条件,之后也会通过判断条件,进入到方法又创建一个实例
-
可以 对方法进行加锁
,保证多线程下仅创建一个实例
// 懒汉模式 + synchronized同步锁
public final class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
-
同步锁
会增加 锁竞争
,带来系统 性能开销
,从而导致系统性能下降,因此这种方式也会 降低单例模式的性能
-
可以考虑将同步锁放在if条件里面, 减少锁粒度
,进而 减少同步锁的资源竞争
// 懒汉模式 + synchronized同步锁
public final class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
-
上述代码依然会 创建多个实例
,因为在 多线程
并发情况下,可以同时通过if判断条件
-
因此需要在同步锁里面再加一个判断条件,即
Double-Check
// 懒汉模式 + synchronized同步锁 + Double-Check
public final class Singleton {
private static Singleton instance = null;
public List<String> list;
private Singleton() {
list = new ArrayList<>();
}
public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
-
在JVM中, 重排序
是非常重要的一环,尤其在并发编程中,JVM可能会进行任意排序以 提高程序性能
-
上面代码
instance = new Singleton()
的执行过程
- 给Singleton分配内存
- 调用Singleton的构造函数来初始化成员变量(即list)
-
将Singleton对象指向分配的内存空间(执行完之后instance为 非null
)
-
如果JVM发生重排序优化,上面的步骤3可能会发生在步骤2之前
-
如果初始化线程A刚好完成步骤3,而步骤2没有进行时,又有另一个线程B到了 第一次判断
-
线程B判断instance为非null,直接返回对象( 未完成构造
)使用,可能会导致 异常
-
synchronized只能保证可见性、原子性,但无法保证同步块内执行的顺序!!
-
volatile
可以保证线程间变量的可见性,同时还可以阻止 局部重排序
的发生,代码如下
// 懒汉模式 + synchronized同步锁 + Double-Check
public final class Singleton {
private volatile static Singleton instance = null;
public List<String> list;
private Singleton() {
list = new ArrayList<>();
}
public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
内部类
// 懒汉模式 + 内部类实现
public final class Singleton {
public List<String> list;
private Singleton() {
list = new ArrayList<>();
}
public static class InnerSingleton {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return InnerSingleton.instance;
}
}
原文
http://zhongmingmao.me/2019/09/17/java-performance-singleton/