0
点赞
收藏
分享

微信扫一扫

三、单例模式:双重检查锁和静态内部类的两种写法

一、双重检查锁

先看看常见错误写法:

public class Singleton {
private static Singleton instance = null;

private Singleton(){}

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

错误解析
  由于指令重排优化,可能会导致初始化单例对象将该对象地址赋值给 instance 字段的顺序与上面 Java 代码中书写的顺序不同。例如,线程 A 在创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时线程 A 就可以将分配的内存地址赋值给 instance 字段了,然而该对象可能还没有初始化。线程B来调用 ​​getInstance()​​ 方法,得到的就是未初始化完全的单例对象,这就会导致系统出现异常行为。

备注:上面所说的初始化单例对象将该对象地址赋值给 instance 字段是指:​​instance = new Singleton()​​​。这段代码涉及到两个指令,初始化单例对象:​​new Singleton()​​​;将该对象地址赋值给 instance 字段:​​instance = 内存地址​​。在构造方法被调用之前就是指​​new Singleton()​​。

  为了解决该问题,我们可以使用 ​​volatile​​​ 关键字修饰 ​​instance​​​ 字段。​​volatile​​ 关键字的一个语义就是禁止指令的重排序优化,从而保证 ​​instance​​ 字段被初始化时,单例对象已经完全被完全初始化。最终得到的代码如下所示:

public class Singleton {
/**
* 使用 volatile 关键字修饰,避免指令重排序
*/
private volatile static Singleton instance = null;

private Singleton(){}

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

二、静态内部类

public class Singleton {
private Singleton() {}

private static class SingletonHolder{
private static Singleton instance = new Singleton();
}

public static Singleton getInstance() {
return SingletonHolder.instance;
}
}

熟悉 Java 类加载机制的都知道,当第一次访问类中的静态字段时,会触发类加载,并且同一个类只加载一次。静态内部类也是如此,类加载过程由类加载器负责加锁,从而保证线程安全。此种单例模式更加简洁明了,不容易出错。


举报

相关推荐

0 条评论