单实例和懒加载

单实例和懒加载

单实例是某个类在全局环境中只创建一个实例。而懒加载,则是在使用到某个类的实例时才创建该实例。很多面试官会让实现一个单实例,进而实现一个线程安全的单实例。但是普通的单实例就是线程安全的,说明他们想的已不是简单单实例,而是涉及到懒加载的、线程安全的单实例。

单实例

(强制)单实例的实质有两点:类的构造方法应该是私有的,外部无法显式创建,要有效防止创建多个实例;使用类静态常量作为该实例等引用,保证只有一个引用。如下:

  • 方法一优点:一目了然

    1
    2
    3
    4
    5
    6
    public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() {

    }
    }
  • 方法二优点:getInstance方法可以在返回单实例前做其他处理,如可以针对每个线程返回不同的单实例,返回不同的范型实例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() {

    }
    public static Elvis getInstance() {
    return INSTANCE;
    }
    }

以上两个方法,性能上没有区别,因为jvm实现已经将静态工厂方法作了内联处理。

还有一点需要注意,如果该类实现了Serializable接口,那么反序列时可能会破环单实例模式。解决方法如下:

1
2
3
4
5
6
7
8
9
public class Elvis implements Serializable {
public transient static final Elvis INSTANCE = new Elvis();
private Elvis() {

}
private Object readResolve() {
return INSTANCE;
}
}
  • 另外一种解决方法是使用枚举类(1.5之后),强烈建议使用这种方式,可以保证单实例、避开序列化问题和反射攻击。

    1
    2
    3
    4
    5
    6
    public enum Elvis {
    INSTANCE;
    private Elvis() {

    }
    }

懒加载

懒加载实质:第一次获取时才创建资源。与单实例不同,单实例在类加载时,由于该实例为静态变量,所以会被初始化。所以懒加载不能将资源作为外部类的静态变量。使用那么有一个问题,多个线程同时去获取资源,如何防止资源被多次创建,破坏延迟加载的功效?

  • 方法一:线程安全的单实例懒加载,内部类实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public Singleton {
    private static class FieldHolder {
    static final Singleton INSTANCE = new Singleton();
    }
    private Singleton() {

    }
    static Singleton getInstance() {
    return FieldHolder.INSTANCE;
    }
    }
  • 方法二:线程安全的懒加载,Double-check实现,只在1.5之后安全可用(volatile的禁止指令重排)。volatile保证在第二次检查前,从主内存重新获取instance。以下是延迟创建实例变量,也可以用来延迟加载静态变量,但是用方法一更不错。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public Singleton {
    private static volatile Singleton instance;
    private Singleton() {

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