设计模式之单例补遗

懒汉(双重检查锁定)方式的详解补充

书接上回,还是先把代码再贴一次。

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

关键之处还是在声明instance变量时的volatile关键字。因为咋看起来,如果没有这个volatile似乎程序逻辑上也没有什么漏洞。但是,由于instance = new LazySingleton()的操作并不是原子的,而是按以下步骤执行:

  1. 为新对象分配内存空间;
  2. 构造并初始化对象;
  3. 把instance指向步骤1中分配的内存空间。

除此之外,Java运行时环境的JIT编译器会做指令重排序操作,即生成的机器指令与字节码指令顺序不一致。在单线程条件下,即使是步骤2、3发生了重排序,可以保证最终结果保证一致,也就是instance指向来初始化后的对象。而在并发场景下,一旦发生了重排序操作,就有可能出现instance指向了步骤1分配的内存,单尚未完成步骤2的初始化。

此时就轮到『volatile』闪亮登场了。Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

如果希望对java的重排序有更多了解,可以先读一下这篇文章

枚举实现

虽然已经提供了很多种的单例实现方法,但是(在Java中)最简洁的莫过于使用枚举来实现了。

public enum EnumSingleton {
    INSTANCE;
    
    public void Method() {}
}

不仅代码简洁,而且JVM来保证了其线程安全,并且无偿地提供了序列化机制,绝对防止多次实例化,是一种高效安全的单例模式的实现。

但是这种写法我是很少使用的,纯属个人习惯问题,总觉得写法上有些怪异,使用起来有心里负担,而且其它语言中似乎也没见过有这样的用法。

扩展单例(多例?)

之前我们讨论的都是纯粹的单例,应用程序生命周期中只存在一个实例。而现在我们需要做一下扩展,将程序改造为允许在其声明周期内存在指定个数的实例,是一种类似于数据库连接池这样的概念。明确概念之后,代码实现也比较简单。

public class Singleton {

    private static int size = 5;
    private static ArrayList<E> instanceList = new ArrayList(size);

    static {
        for(int i = 0; i < size; i++) {
            instanceList.add(new Singleton());
        }
    }
    private Singleton(){}

    public static Singleton getInstance(){
        Random random = new Random();

        return (Singleton) instanceList.get(random.nextInt(size));
    }
}

后记

关于单例模式,暂告一段落,后面再继续聊聊其它的模式。

TO BE CONTINUED

知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

posted @ 2017-11-01 16:44  BigMan85  阅读(120)  评论(0编辑  收藏  举报