在日常工作中,有很多对象,我们只需要一个。比如:线程池, 缓存,注册表等。如果制造出多个实例,就会导致许多问题,如程序行为异常,资源使用过量等。这就需要对对象的构建进行控制,使其只能产生一个对象。这就是本篇要讲的设计模式——singleton(单例)。
单例模式的定义:确保只有一个类只有一个实例,并提供一个全局访问点。
那么,要如何实现单例模式,使得一个类只能产生一个对象呢?请看下面的实现:
public class Singleton { private static Singleton s; private Singleton() { } public static Singleton getInstance() { if (s == null) { s = new Singleton(); } return s; } }
你或许已经看懂了上面的代码。没错!我们可以通过将类的构造函数设成private访问权限,这样其他类就无法随意创建该类的对象了。然后我们应用一个静态的类对象来作为唯一的一个对象,并作为全局访问点。
clone()方法
还有一件事值得注意:类的clone()方法。例如, 如果基类实现了cloneable接口的话,子类就应该重写该方法。防止类的对象被copy,破坏单例特性。当然,在应用中应该灵活运用各种方法来防止clone()的各种情况。
多线程访问singleton方法
对于上面的实现方法,如果在多线程中被访问,可能会产生问题。
public class Singleton { private static Singleton s; private Singleton() { } // 如果多个线程同时访问, 有可能会出现多个实例。 public static Singleton getInstance() { // 第一次初始化时,多个线程同时执行"if (s == null)",判断结果都为真,所以都会执行下面的操作:"s = new Singleton()",由此引发多个实例的出现。 if (s == null) { s = new Singleton(); } return s; } }
那么该如何解决单例模式在多线程程序中的问题呢?
方案1:
将getInstance()变成同步(synchronized)方法,多线程的灾难可以轻易解决。
public class Singleton { private static Singleton s; private Singleton() { } public static <span style="color:#FF0000;">synchronized</span> Singleton getInstance() { if (s == null) { s = new Singleton(); } return s; } }
不推荐该方案! 因为对单例初始化只需要一次,这样做将会使得每次调用getInstance方法时都会进行同步。对运行效率是极大的累赘。
方案2:
在静态初始化时创建单例。
public class Singleton { // Early initialization.定义的时候就初始化。 private static Singleton s = new Singleton(); private Singleton() { } public static Singleton getInstance() { return s; } }
该方法可以保证单例。但我们对程序设计时,通常保持直到使用时才创建对象的原则。
方案3:
用“双重检查加锁”,在getInstance()中减少使用同步。
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { <span style="color:#FF0000;">if (singleton == null) { </span> //必须 singleton = new Singleton(); } } } return singleton; } }
这样将只有第一次创建对象时同步,既保证了线程安全,同时又保证了执行效率。
有人问为什么要在synchronized 内再一次判断if(sinlenton == null),我个人理解是这个原因:
如果两个线程都执行到了第一个if(singleton == null) 且都为true,这是总有一个线程先进入synchronized (Singleton.class)...部分代码,并阻塞另一个进入。当第一个进入后并成功建了一个实例singleton后,不再阻塞。另一个线程将进入执行synchronized (Singleton.class)...代码(因为它已经执行了第一个singleton==null 并为true)。如果里面不再进行判断,则会再一次创建一个singleton。
所以里面再一次判断了一次。
参考:
《head first 设计模式》
http://blog.csdn.net/natee/article/details/4408245#quote