单例模式
单例模式也许是最常见的一种设计模式了。看起来简单,实际上如果不注意细节,产生的问题,相对还是有些复杂的。
在软件系统中,我们希望某些类始终保持最多一个实例对象的存在,来保证一致性或者节约内存等。这时,就用到了单例模式。
单例模式的做法是,将类的构造方法私有化,不对外开放。在类的内部定义一个该类的静态实例成员。通过一个静态方法对外提供该成员。
但这里就有一些问题了,这个实例化对象什么时候创建?
通常有两种方法:饿汉式/懒汉式
- 饿汉式:在定义时创建 instance = new A();
- 懒汉式:在getInstance时,判断,如果没有实例化,先实例化再返回。称为延迟加载。
- if( instance == null ) { instance = new A(); } return instance;
- 饿汉式在类初始化时就创建了instance实例,无法做到延迟加载。无论后面是否使用,都占用着内存。
- 懒汉模式在多线程场景下,会创建多个实例,或者因为重排序在没有实例化完成时,却返回了该对象的引用,导致使用时出错。
private static Something instance = null;
public Something getInstance() {
if (instance == null) {
synchronized (this) {
if (instance == null)
instance = new Something();
}
}
return instance;
}
这段代码还是会因为重排序,导致new的对象没有初始化完成,却已经将引用赋给了instance。其他线程拿到一个没有初始化完成的实例,导致使用出错。在1.5的jvm后,volatile关键字可以解决这个问题。如果将上述代码中的instance用volatile修饰,由于volatile变量的读写操作间存在happens-before关系,因此可以阻止重排序,使其他线程拿到的一定是初始化完成的实例。但是,volatile在拥有如此强大的功能的同时,他的性能开销也有很大程度的上升,已经快达到了同步的级别,所以这种方式虽然没有逻辑上的错误,但仍不是一个最近好的选择。
人类是聪明的动物,总能想到解决为的办法。IODH技术,很好的解决了上面说的所有问题。代码如下:
public class IODHCode {
private static class Gener{private static IODHCode instance = new IODHCode();}public static IODHCode getInstance() {return Gener.instance;}
}
这种技术利用了内部静态类,内部静态类在调用它的时候才进行加载,并且由jvm保证其线程安全性。这样既做到了延迟加载,又在代码层面没有加锁,不影响性能。
优点:
- 提供对唯一实例的控制
- 节约资源
缺点:
- 职责过重,既当工厂,又当产品
- 长时间不用,会被垃圾回收,导致共享状态丢失。(有争议)