java中singleton模式与延迟初始化实现方式总结
来源:互联网 发布:c语言复合语句 编辑:程序博客网 时间:2024/06/05 19:40
singleton模式是常用的设计模式之一。最原始的实现方式如下:
public class singleton {private static singleton instance;private singleton(){}public static singleton getInstance(){if(instance == null)instance = new singleton();return instance;}}
这种方式在多线程的情况下会出现实例化多个,比如如下调度模式:
Thread 1Thread 2测试singleton==null通过 测试singleton==null通过执行new singleton()构造函数 执行new singleton()构造函数如此会出现两个singleton对象。一个惯用的修复方式为给getInstance方法加上synchronized同步锁:
public class singleton {private static singleton instance;private singleton(){}public static synchronized singleton getInstance(){if(instance == null)instance = new singleton();return instance;}}这个方式能够修复上面的问题,但是会带来较大的性能损失。因为每一次调用getInstance时都必须要进出synchronized块,而事实上除了第一次调用需要同步外,其他时候都不需要同步。为了解决这个问题,有了著名的“Double-Checked Locking”。
public class singleton {private static singleton instance;private singleton(){}public static singleton getInstance(){if(instance == null)synchronized(singleton.class){if(instance == null)instance = new singleton();}return instance;}}但这种方式实际上是有问题的,问题出在instance = new singleton();这一句上,这一句实际上有若干语句构成。
使用javap -c singleton.class命令可以得到singleton类的字节码:
Compiled from "singleton.java"public class singleton { public static singleton getInstance(); Code: 0: getstatic #2 // Field instance:Lsingleton; 3: ifnonnull 38 6: ldc_w #3 // class singleton 9: dup 10: astore_0 11: monitorenter 12: getstatic #2 // Field instance:Lsingleton; 15: ifnonnull 28 18: new #3 // class singleton 21: dup 22: invokespecial #4 // Method "<init>":()V 25: putstatic #2 // Field instance:Lsingleton; 28: aload_0 29: monitorexit 30: goto 38 33: astore_1 34: aload_0 35: monitorexit 36: aload_1 37: athrow 38: getstatic #2 // Field instance:Lsingleton; 41: areturn Exception table: from to target type 12 30 33 any 33 36 33 any static {}; Code: 0: aconst_null 1: putstatic #2 // Field instance:Lsingleton; 4: return }其中
12: getstatic #2 // Field instance:Lsingleton; 15: ifnonnull 28 18: new #3 // class singleton 21: dup 22: invokespecial #4 // Method "<init>":()V 25: putstatic #2 // Field instance:Lsingleton;为synchronized同步块中的操作。第18行为即将创建的singleton对象,但此时并没有调用构造函数。第22行invokespecial调用构造函数。第25行把该对象的引用赋值给instance。如果代码严格按照上述顺序运行,则这个方法就没有问题。但是jvm在程序运行时会进行优化,一个典型的优化就是指令重排。指令重排能够使JVM根据处理器的特性(CPU的多级缓存系统、多核处理器等)适当的重新排序机器指令,使机器指令更符合CPU的执行特点,最大限度的发挥机器的性能。指令重排序的标准是在单线程下,保证程序的效果不变。
对synchronized同步块中的代码指令重排:
12: getstatic #2 // Field instance:Lsingleton; 15: ifnonnull 28 18: new #3 // class singleton 21: dup 25: putstatic #2 // Field instance:Lsingleton; 22: invokespecial #4 // Method "<init>":()V
由于指令的重排序,第25行的给instance赋值可能先于22行的invokespecial的对构造函数的调用,导致instance虽然已被赋值而不为null,但其引用指向的对象并没有被正确初始化。有可能导致另一个线程调用getInstance时得到的是未被正确初始化的对象。
Thread 1Thread 2得到singleton.class的锁,进入同步块。 根据指令重排后的代码执行到putstatic,将new创建的singleton对象赋值给instance,但此时invokestatic语句并没有被执行,singleton的构造方法并没有被调用,该singleton对象并没有被正确初始化。 调用getInstance方法,测试instance不为null,因此返回instance,而此时instance指向的singleton对象的初始化并没有完成。 由于使用了未被正确初始化的singleton对象,程序可能出错。
针对这种问题,有一种改进的方法,试图绕开指令重排的干扰。但该方法是无效的。
public class singleton {private static singleton instance;private singleton(){}public static singleton getInstance(){if(instance == null)synchronized(singleton.class){singleton temp = null;if(instance == null)temp = new singleton();instance = temp;}return instance;}}
该方法使用一个临时变量来创建singleton对象,然后再赋值给instance。初看之下似乎可行,但是jvm在运行时可能把
temp = new singleton();instance = temp;这两行代码优化成instance = new singleton();来执行,实际上与刚才没有差别。(来自http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization中的评论,指出JIT在运行时会进行所有经典的优化:http://www.oracle.com/technetwork/java/whitepaper-135217.html#serverThe optimizer performs all the classic optimizations, including dead code elimination, loop invariant hoisting, common subexpression elimination, constant propagation, global value numbering, and global code motion 上面这种方式应该属于common subexpression elimination)
针对这种优化,又有一种改进:
public class singleton {private static singleton instance;private singleton() {}public static singleton getInstance() {if (instance == null)synchronized (singleton.class) {singleton temp = null;if (instance == null)synchronized (singleton.class) {temp = new singleton();}instance = temp;}return instance;}}这个方法使用内嵌的synchronized同步块来隔开可能被优化的两行语句。但是这种方法仍然不可行。这是因为synchronized同步块的语法为同步块中的语句执行完前不会释放锁,并没有限定synchronized同步块后的语句必须在锁释放后执行。所以JVM可能把instance=temp放到同步块中,这样就回到了上一种情况。
针对上一种情况的又一种改进:
public class singleton {private static singleton instance;private singleton() {}public static singleton getInstance() {if (instance == null)synchronized (singleton.class) {singleton temp = null;if (instance == null)temp = new singleton();synchronized (singleton.class) {instance = temp;}}return instance;}}这种方法几乎已经可行,但仍然有一些微妙的可能导致多个singleton对象被创建。因为对于多核处理器,每个线程都有自己的cache,可能一个线程已经创建了instance对象,而另一个线程的cache中保存的instance副本仍然是null,于是这个线程仍然可能继续创建一个新的singleton。不过这种行为在理论上存在,在实践中极难观察到。事实上这一种解释就能够推翻以上所有的对Double-Checked Locking尝试。
以上两个方法及其无效的解释来自http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
对于“Double-Checked Locking”,自java5收紧volatile的语意后,使用volatile关键字修饰instance能够修复以上的问题。
public class singleton {private static volatile singleton instance;private singleton(){}public static singleton getInstance(){if(instance == null)synchronized(singleton.class){if(instance == null)instance = new singleton();}return instance;}}java5以后,对于以volatile修饰的变量,对其的读写都不可指令重排,而且volatile亦保证了线程不会因为cache而创建多个对象。
此外,这个方法可以优化性能:
public class singleton {private static volatile singleton instance;private singleton(){}public static singleton getInstance(){singleton result = instance;if(result == null)synchronized(singleton.class){result = instance;if(result == null)instance = result = new singleton();}return result;}}将instance读取到result中,保证volatile修饰的变量只读取一次,可以提高性能。此方法来自Effective Java
以上方法亦可使用在延迟初始化实例的属性上,只要将属性和get方法上的static去掉即可。
对于java5之前的jvm,就必须在以下两个选项中权衡选择:
1.在get方法上使用synchronized
public class singleton {private attribute instance;public synchronized attribute getInstance(){if(instance == null)instance = new attribute();return instance;}}class attribute{}接受其性能上的损失。
2.在对象初始化时初始化该属性
public class singleton {private attribute instance = new attribute();public attribute getInstance() {return instance;}}class attribute {}放弃延迟初始化。
对于延迟初始化静态变量和单例模式,可以使用lazy initialization holder class模式(也称作initialize-demand holder class idiom模式),出自Effective Java
单例模式:
public class singleton {private singleton(){}private static class instanceHolder{static singleton instance = new singleton();}public static singleton getInstance(){return instanceHolder.instance;}}延迟初始化静态变量:
public class singleton {private static attribute instance;private static class instanceHolder{static attribute instance = new attribute();}public static attribute getInstance(){return instanceHolder.instance;}}class attribute{}注意这个方法,去掉属性和get方法上的static修饰符,通过对象调用get方法似乎也能运行。但这样的话,对于不同的对象,属性instance都指向了同一个attribute对象,是与最初的需求不相符的。
- java中singleton模式与延迟初始化实现方式总结
- Java中实现Singleton模式
- Singleton模式实现方式
- C#中实现Singleton模式(静态初始化策略)
- Java单例模式与延迟加载分析 Singleton & Lazy Initialized
- java中singleton的几种实现方式
- java中Singleton的几种实现方式
- 设计模式:单例模式(Singleton) java实现及实现方式对比
- Singleton模式的几种实现方式
- singleton模式的几种实现方式
- Singleton模式的各种实现方式。
- Singleton模式的实现方式 C#
- JAVA设计模式-单例模式(Singleton)的五种实现方式
- 通过延迟加载实现Singleton
- Singleton - 用“静态初始化”在 .NET 中实现 Singleton & 多线程 Singleton
- Java中设计模式(2) Singleton模式
- java单例模式singleton方法总结
- Singleton模式与在Ogre中的实现
- 创建你的第一个Android项目
- Mastering Algorithms with C中文版附带源码说明
- MFC 读取文件内容的其中两种方式
- Open source 20
- #、##和__VA_ARGS__
- java中singleton模式与延迟初始化实现方式总结
- C++编程对缓冲区的理解
- C++ 简单TCP实例
- Documentation/power/s2ram.txt
- ubuntu 8.10安装配置经验(Intrepid Ibex)一
- android handler详解
- JAP 注解详解
- [转]vim snipMate
- linux下杀死进程(kill)的N种方法