很多人熟知单例模式中有一种写法是使用双重检查锁实现的,但是在网上看到的例子基本上都不正确,有些写是正确,但没有很好解析,造成很多人没有真正理解。其中,典型错误写法是这样的:
public class Resource {
private static Resource resource ;
public static Resource getInstance(){
if(resource == null ){
synchronized (Resource.class) {
if(resource == null ){
resource = new Resource() ;
}
}
}
return resource ;
}
private Resource(){}
}
它基本思路是,首先在没有同步的情况下检查resource是否等于null,如果条件不成立,直接返回resource 。否则,就使用同步再检查resource是否等于null,条件成立才正真初始化resource。这中方式既保证只有一个线程初始化resource,又能做到延时加载。似乎是“鱼和熊掌可兼得“。
上面程序真正的问题是没有同步的情况下读取共享变量resource,并发的情况下对象的状态值有可能是过期无效的。要解决这个问题也很简单,把resource声明为volatile类型。volatile有什么作用?引用《java并发编程实战》的解析:
当一个域声明为volatile类型后,编译器与运行时会监视这个变量:它是共享的,而且对它的操作不会与其他的内存操作一起被重排序。volatile变量不会缓存在寄存器或缓存在对其他处理器隐藏的地方。所以,读一个volatile类型的变量时,总会返回由某一线程所写入的最新值。
读取volatile变量比读取非volatile变量的性能几乎没有差别,不过需要注意的是volatile只能保证内存可见性,并不能保证原子性。
《effective java 》是不支持这种写法的,推荐使用”惰性初始化holder“或”枚举“技巧,如:
public class Resource {
private static class ResourceHolder{
private static Resource resource = new Resource() ;
}
private static Resource resource ;
public static Resource getInstance(){
return ResourceHolder.resource ;
}
private Resource(){}
}
参考资料
《effective java 》第二版
《java并发编程实战》
《研磨设计模式》