设计模式之单例补遗
懒汉(双重检查锁定)方式的详解补充
书接上回,还是先把代码再贴一次。
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()的操作并不是原子的,而是按以下步骤执行:
- 为新对象分配内存空间;
- 构造并初始化对象;
- 把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 国际许可协议进行许可。