单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
实现重点:私有构造器,一个静态方法和一个静态变量。
实现单例模式的方法有很多种,主要分析一下使用双锁机制实现的方式。
该方式是lazy初始化,且线程安全。

public class Singleton {
    private volatile static Singleton singleton; // A

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) { // B
            synchronized (Singleton.class) { // C
                if (singleton == null) { // D  防止排队等锁的对象进入同步块后又new一个对象出来导致得到不同的实例
                    singleton = new Singleton(); // E 分配空间,初始化对象,将变量指向分配的地址空间
                }
            }
        }
        return singleton;
    }
}

若有多个线程同时调用了getInstance()方法,那么语句B处都判断为null,可能有多个线程会到语句C处,而只有一个线程能够进入到同步块,其他线程需要排队等待其退出。进入同步块之后,需要再次判断实例是否为null,为空才new一个实例。
为什么同步块中需要再次判断singleton是否为null?
因为可能有多个线程在等待进入同步块,第一个线程进入同步块之后,new了一个实例,那么此时singleton已经不为null;若不再次判断singleton是否为null,则后续排队进入同步块的每个线程都会重新执行singleton = new Singleton()语句,导致每次得到的实例都不一样。
为什么singleton需要被声明为volatile变量?
先分析若不声明为volatile变量会出现什么情况。注意到singleton = new Singleton()这条语句,实际上在执行的时候分为好几步:为对象分配空间,初始化对象,让变量指向分配的地址空间。因为存在指令重排序的问题,所以在实际执行的时候,顺序可能变为为对象分配空间,让变量指向分配的地址空间,然后进行初始化。那么在线程A中,变量已经指向分配的地址空间但还未未初始化,此时另一个线程执行到语句B处,判断出singleton != null,于是return singleton;但实际上它得到的是还未初始化的对象。可以通过将变量声明为volotile来避免这种情况。volatile禁止了指令重排序,也就是语句E会按照为对象分配空间,初始化对象,让变量指向分配的地址空间的顺序进行,那么在线程B看来,singleton对象的引用要么指向null,要么指向一个初始化完毕的Instance,而不会出现某个中间态,保证了安全。