单例模式

单例模式有两种:“饿汉模式”和“懒汉模式”。直接使用饿汉模式,懒汉模式在任何情况下都不需要使用,因为饿汉模式先天就没有线程安全问题。

懒汉单例模式终极版

用volatile和synchronized配合双重检查锁(DCL:double check lock)机制,其中volatile用来保证多线程下的可见性:

  • 读volatile:每当子线程某一语句要用到volatile变量时,都会从主线程重新拷贝一份,这样就保证子线程的跟主线程的一致。
  • 写volatile: 每当子线程某一语句要写volatile变量时,都会在读完后同步到主线程去,这样就保证主线程的变量及时更新。

或者认为用volatile修饰singleton并不是用了volatile的可见性,而是用了java内存模型的“先行发生”(happens-before)原则的其中一条Volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”指时间上的先后顺序。这样禁止指令重排序,确保singleton对象是在初始化完成后才能被读到。

public class Singleton {
private static volatile Singleton uniqueInstance;

private Singleton(){
}

public static Singleton getInstance(){
if(uniqueInstance == null){ //#1
synchronized(Singleton.class){ //#2
if(uniqueInstance == null){ //#3
uniqueInstance = new Singleton(); //#4
System.out.println(Thread.currentThread().getName() + ": uniqueInstance is initalized..."); //#5.1
} else {
System.out.println(Thread.currentThread().getName() + ": uniqueInstance is not null now..."); //#5.2
}
}
}
return uniqueInstance;
}
}

饿汉单例模式

以下两种写法在编译后等效

public class Singleton {
    private static final Singleton singleton=new Singleton();
    
    public static Singleton getInstance(){
        return singleton;
    }
}
public class Singleton {
    private static Singleton singleton;
    
    static{
        singleton=new Singleton();
    }
    
    public static Singleton getInstance(){
        return singleton;
    }
}

类的加载分为5个步骤:加载、验证、准备、解析、初始化,“饿汉模式”的创建对象是在类加载的初始化阶段进行的,jvm规范规定有且只有以下7种情况下会进行类加载的初始化阶段:

  • 使用new关键字实例化对象的时候
  • 设置或读取一个类的静态字段(被final修饰,已在编译器把结果放入常量池的静态字段除外)的时候
  • 调用一个类的静态方法的时候
  • 使用java.lang.reflect包的方法对类进行反射调用的时候
  • 初始化一个类的子类(会首先初始化父类)
  • 当虚拟机启动的时候,初始化包含main方法的主类
  • 当使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

基本来说就是只有当以某种方式调用了这个类的时候,它才会进行初始化,而不是说jvm启动的时候就初始化。所以说假如单例类里只有一个getInstance()方法,那相当于当从其他类调用getInstance()方法的时候才会进行初始化,这和“懒汉模式”一样的效果。而jvm本身会确保类的初始化只执行一次。

当单例类里除了getInstance()方法还有一些其他静态方法,这样当调用其他静态方法的时候,也会提前初始化实例了。解决这个问题只要加个内部类就行了,这种模式叫holder pattern,如下,只有当调用getInstance()方法的时候,才会初始化内部类SingletonHolder:

public class Singleton {
    private static class SingletonHolder{
        private static final Singleton instance=new Singleton();
    }
    
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

单元素enum来实现单例

public enum Singleton
{
    INSTANCE;

    public void dosomething()
    {
        System.out.println(this + " is speaking!");
    }
}

发表评论