实现单例模式的最优方案。
双重检查锁定
1 | public class Singleton { |
volatitle的作用
instance = new Singleton();
通过这一行代码创建一个对象,可以分解为如下的三行伪代码:1
2
3memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
上面三行伪代码中的2和3之间,可能会被重排序(在一些JIT编译器上,这种重排序是真实发生的)2和3之间重排序之后的执行时序如下:1
2
3
4memory = allocate(); //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址
//注意,此时对象还没有被初始化!
ctorInstance(memory); //2:初始化对象
如果发生重排序,另一个并发执行的线程B就有可能在第一次为空判断instance时不为null。线程B接下来将访问instance所引用的对象,但此时这个对象可能还没有被A线程初始化!
当声明对象的引用为volatile后,上面的三行伪代码中的2和3之间的重排序,在多线程环境中将会被禁止。
临时变量的作用
通过前面的对 volatile 关键字作用解释可知,访问 volatile 变量,需要保证一些执行顺序,所以的开销比较大。这里定义一个临时变量,在 instance 不为空的时候(这是绝大部分的情况),只要在开始访问一次 volatile 变量,返回的是临时变量。在运行过程中,除了第一次以外,其他的调用只要访问 volatile 变量 instance 一次,这样能提高 25% 的性能(Wikipedia)。如果没有此临时变量,则需要访问两次,从而降低了效率。
基于类初始化的解决方案
1 | public class Singleton { |
延迟初始化,这里是利用了 Java 的语言特性,内部类只有在使用的时候,才会去加载,从而初始化内部静态变量。关于线程安全,这是 Java 运行环境自动给你保证的,在加载的时候,会自动隐形的同步。在访问对象的时候,不需要同步 Java 虚拟机又会自动给你取消同步,所以效率非常高。
参考链接:http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization
http://www.race604.com/java-double-checked-singleton/