最近在一个项目当中遇到了需要一个单实例模型的问题,就认真查了下资料,复习了下java的单例设计
1.饿汉式:在系统启动的时候就讲对象 实例化。
public class Test{
private static Test instance=new Test();
private Test(){}//必须定义为私有的构造方法 使得不能再外部new一个实例
public static Test getInstance(){
return instance;
}
}
2.懒式:在第一次使用到的时候去判断是否为空,然后去实例化
public class Test{
private static Test instance=null;
private Test(){}//必须定义为私有的构造方法 使得不能再外部new一个实例
public static Test getInstance(){
if(instance==null){
instance=new Test();
}
return instance;
}
}
但是 第二种方法只适用于单线程的程序代码当中,不能保证在多线程访问的服务器端程序中运行时候保证单实例。所以在网上出现了将 第二种方法改造为同步锁定的代码如下:
public static synchronized Test getInstance(){
if(instance==null){
instance=new Test();
}
return instance;
}
但于此同时也产生了为了保持同步,必须付出昂贵的代价。事实上只有在第一次使用的时候才用到同步,但是如果把同步加到方法上,就在每一次调用获取实例化对象的时候都将产生同步,这样是在服务器端非常耗资源的。
一些聪明的程序员为了解决这个问题,发明了一个新的名词:“双重检查锁定”,代码如下
public class Test{
private static Test instance=null;
private Test(){}//必须定义为私有的构造方法 使得不能再外部new一个实例
public static Test getInstance(){
if(instance==null){
synchronized (Test .class){
if(instance==null){
instance=new Test();
}
}
}
return instance;
}
}
双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。
多线程访问的时候会 返回一个非null但是没有被实例化的残缺对象
为避免单例中代价高昂的同步,程序员非常聪明地发明了双重检查锁定习语。不幸的是,鉴于当前的内存模型的原因,该习语尚未得到广泛使用,就明显成为了一种不安全的编程结构。重定义脆弱的内存模型这一领域的工作正在进行中。尽管如此,即使是在新提议的内存模型中,双重检查锁定也是无效的。对此问题最佳的解决方案是接受同步或者使用一个 static field
本文来自IBM一位开发工程师的日志 详情请参阅:http://www.ibm.com/developerworks/java/library/j-dcl.html 以及 http://ajava.org/course/java/13502.html