先说单例模式:
经典的单例模式实现:
饿汉式:
public class Singleton { private static Singleton instance = new Singleton(); public static Singleton getInstance(){ return instance; } }
懒汉式:
public class Singleton { private static Singleton instance = null; synchronized public static Singleton getInstance(){ if(instance ==null){ instance = new Singleton(); } return instance; } }
这两种都是可以安全运行在多线程下的。但是每一个都有点缺点,对于第一种如果这个单例的初始化需要很多内存和时间,我们希望用到在初始化,没有用到就不初始化。对于第二种我们,其实只需要在第一次初始化时需要避免线程冲突,其他时候都可以直接返回的,而第二种的实现则变成了完全的串行(因为每一个操作都需要获得对象锁),非常大的降低了并发度。
我们尝试以下改进:
最好的方式是DCL(double-checked locking),这种方式的实现如下:
public class Singleton { private static Singleton instance = null; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) instance = new Singleton(); } } return instance; } }
这样看起来是完美的解决方案,确实DCL是一个很好的解决分类处理的思想,在C下其能很好的运行,但在Java下就会有问题了。这全怪Java的JMM(Java内存模型)。
在执行到instance = new Singleton()这里时,由于Java内存的“无序写入”,
可能的执行顺序是这样的:
mem = allocate(); //Allocate memory for Singleton object.
instance = mem; //Note that instance is now non-null, but has not been initialized.
ctorSingleton(instance); //Invoke constructor for Singleton passinginstance.
即为instance分配内存,标记instance不为空,初始化instance。
这样会导致,一个线程刚标记完,还没有初始化赋值给instance,就释放了锁,然后另一个线程进入锁,判断不为空,释放锁,返回instance,这时显然是错的。这里出现这中错误的原因是instance = new instance();并没有真正的执行完,就释放了锁,我实在不能理解这样设计的原因,但很好的是在JDK1.5之后,已不存在这种问题了DCL这个可以很好的运行。但我们还是有必要继续讨论JDK1.5之前如何实现的。
可以在instance返回之前加一个步奏,确定其确实初始化了。
public class Singleton { private static Singleton instance = null; public static Singleton getInstance() { if (instance == null) { Singleton temp = instance; synchronized (Singleton.class) { if (temp == null) instance = new Singleton(); } instance = temp; } return instance; } }
这样就很好的解决这个问题了。但是代码量和可阅读性已经陡然上升了,那么有没有更好的方法呢?是有的,利用类加载机制来实现,延迟初始化。
public class Singleton { private Singleton() { } private static class SingletonHolder { private static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } }
这里补充一点类加载的知识,类加载分为一个步骤,加载->验证->准备->解析->初始化->使用->卸载。
如果是private static Singleton = new Singleton()这样写,那么初始化发生在
原文:http://www.cnblogs.com/chaiwentao/p/4959648.html