设计模式——单例模式

定义

确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

创建方式

/**
 * 饿汉式
 * 
 * 类加载时,实例就跟创建初始化了,所以是线程安全 (类加载的过程就是线程安全的)
 * 不支持懒加载
 */
public class SingleHunger {
    private static SingleHunger instance = new SingleHunger();

    private SingleHunger() {
    }

    public static SingleHunger getInstance() {
        return instance;
    }
}
/**
 * 懒汉式
 *
 * 线程安全
 * 因为在 getInstance方法加了个锁,那么每次在使用这个单例时,我们都会调用getInstance方法,
 * 那么也就每次都需要加锁、释放锁了。 如果单例使用比较频繁的话,这种方式就不是很好了
 */
public class SingleLazy {

    private SingleLazy instance;

    private SingleLazy(){
    }

    private synchronized SingleLazy getInstance(){
        if (instance == null)
            instance = new SingleLazy();
        return instance;
    }
}
/**
 * 双重校验
 */
public class SingleDoubleCheck {
    private volatile SingleDoubleCheck instance;

    private SingleDoubleCheck(){
    }

    private SingleDoubleCheck getInstance(){
        if (instance == null){
            synchronized (SingleDoubleCheck.class){
                if (instance == null){
                    instance = new SingleDoubleCheck();
                }
            }
        }
        return instance;
    }
}

双重锁校验为什么instance要使用关键字`volatile`
因为 `instance = new Single()` 这句赋值代码不是原子操作,这句代码包含以下3个步骤
1. 给 `new Single()`所即将创建的对象分配内存空间
2. 初始化对象 ,即执行`new Single()`构造方法
3. 把创建对象的内存空间地址赋值给`instance`

编译器指令重排可能会把2、3步的顺序颠倒,这样的话,`Single`对象还没真正初始化,但内存地址已经被赋值给了变量`instance`,
这样其他线程判断`instance`不为空,就直接拿去使用了,但instance其实并没有真正的被初始化,也就出现了问题。
所以使用关键字`volatile`来修饰`instance`禁止指令重排。

`volatile` 解决有序性:
采用部分禁止指令重排。
对于volatile修饰的变量执行写操作,禁止位于其前面的读写操作与其进行重排序;
对于volatile修饰的变量执行读操作,禁止位于其后面的读写操作与其进行重排序
这样,步骤3是对`instance`进行写操作,根据`volatile`指令禁止重排规则,
位于其前面的读写操作禁止与其进行重排序,那么步骤2也就不能和步骤3指令重排序了。

/**
 * 静态内部类
 */
public class SingleStatic {

    private SingleStatic() {
    }

    public static class SingleHandler {
        private static final SingleStatic instance = new SingleStatic();
    }

    public static SingleStatic getInstance() {
        return SingleHandler.instance;
    }
}

为什么这种方式创建的单例是支持懒加载的?
  答:静态变量的初始化是在类加载时,类是在用到它的时候才被加载的。
     所以instance真正初始化  时机是在调用getInstance方法时。
     而且静态变量只会初始化一次,所以我们后续调用getInstance得到的是同一个实例。

为什么是线程安全的?
  答:因为getInstance整个流程都是在类加载过程中完成的,而类的加载机制是线程安全的。

不用静态内部类可以吗?
  答:如果是普通内部类的话,是不可以声明静态变量的。
     如果instance不是静态的,那么就无法在类加载过程中初始化,也就无法保证线程安全了。

你可能感兴趣的:(设计模式——单例模式)