本文出自 http://blog.csdn.net/shuangde800
走进单件模式
有些对象我们只需要一个,比如:线程池,缓存,对话框,处理偏好设置和注册表的对象,日志对象,充当打印机,显卡等设备的驱动程序对象等。
这些类的对象只能有一个实例,如果有多个,然而会导致许多问题产生。
有人可能觉得,这只要用全局变量或者静态变量就可以做到了
这样做的缺点:
必须程序一开始就创建对象,但是有些对象可能用不到,耗费资源。而单件模式在需要时才创建
单间模式的实现
单件模式的经典但简陋(有问题)的实现:
把构造函数设置为私有的,再加一个静态方法用来创建对象并返回。
public Singleton{ private static Sigleton uniqueInstance; // 构造器是私有的,只有类内部方法才能创建类 private Singleton() { } public static MyClass getInstance(){ if(uniqueInstance == null){ // 只有是null的才创建对象 uniqueInstance = new Singleton(); } return uniqueInstance; } }
上面那个做法,会出现问题,原因是多线程产生的问题。
假如有两个线程要几乎同时要创建一个对象,那么将会发生下面的情况:
看上图,发现Thread one和Thread two都各自创建了一个对象!
解决方案1:
把getInstance( )变成同步(synchronized)方法,多线程灾难几乎就可以轻易解决了。
public Singleton{ private static Sigleton uniqueInstance; // 构造器是私有的,只有类内部方法才能创建类 private Singleton() { } // 通过增加synchronized关键字,会迫使每个线程在进入这个 // 方法之前,要先等候别的线程离开该方法。 // 也就是说,不会有两个线程可以同时进入这个方法 public static synchronized MyClass getInstance(){ if(uniqueInstance == null){ // 只有是null的才创建对象 uniqueInstance = new Singleton(); } return uniqueInstance; } }
缺点: 每次用该方法都要同步,会降低拖垮程序的性能。同步一个方法可能会造成程序执行效率下降100倍。
解决方案2:急切实例化
public Singleton{ // 在静态初始化器(static initializer)中创建单件 // 保证了线程安全(thread safe) private static Sigleton uniqueInstance = new Singleton(); // 构造器是私有的,只有类内部方法才能创建类 private Singleton() { } public static synchronized MyClass getInstance(){ return uniqueInstance; } }
解决方案3: 用“双重检查加锁”,在getInstance()中减少使用同步
利用“双重检查加锁”(double-checked locking),首先检查实例是否已经创建了,如果尚未创建,“才”进行同步。
这样,只有第一次会同步,之后再也不会了
public Singleton{ // 注意这里加了volatile关键字 // volatile关键字确保:当uniqueInstance变量被初始化成Singleton实例时 // 多个线程正确地处理uniqueInstance变量 private static volatile Sigleton uniqueInstance; private Singleton() { } public static synchronized MyClass getInstance() { if(uniqueInstance == null) { synchronized (Singleton.class) { if(uniqueInstance == null){ uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
注意! 双重检查加锁不使用于java 1.4以及更早的版本。
定义单件模式
单件模式确保一个类只有一个实例,并提供一个全局访问点
单件模式的类图很简单
全局变量 VS 单件模式
在Java中,全局变量基本上就是对对象的静态引用。
在这样的情况下使用全局变量会有一些缺点,我们已经提到了期中一个:急切实例化 VS.延迟实例化。
但是我们要记住这个模式的目的:确保类只有一个实例并提供全局访问。
全局变量也会变相鼓励开发人员,用许多全局变量指向许多小对象来造成命名空间(namespace)的污染。
单件模式不鼓励这样的现象,但单件仍然可能被滥用。