设计模式之单例模式

来源:互联网 发布:网络看江苏教育频道 编辑:程序博客网 时间:2024/06/06 03:01
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。
单例模式的类图是设计模式中最简单的类图了,事实上,单例模式的类图只有一个类,并且单例模式的目的是保证这个类只有一个实例对象。
通过分析单例模式的特点,我们可以大概知道怎么去实现一个单例模式。首先,我们不能让外界轻易的new一个实例,这个如何来解决呢?我们可以将类的构造器私有化,这样外界就无法通过new来随意实例化这个单例类了。
糟糕,我们的单例类的构造器被私有化了,我们怎么获得单例类的实例呢?别着急,我们可以提供一个静态方法来返回这个单例类的实例。下面是一个简单单例的实现

public class Singleton {      private Singleton(){}     private static Singleton singleton;     public static Singleton getInstance(){           if(singleton == null){               singleton = new Singleton();          }           return singleton ;     }}

上面是一个最简单的单例模式实现,通过私有化构造器来限制外界随意的new单例类的实例,提供静态方法,返回单例类的实例。不过上面这段代码虽然简单,却有一个很严重的问题,就是多线程访问时可能会产生多个实例
例如两个线程同时访问getInstance()方法,线程1执行到if(singleton == null)返回true,开始执行实例化对象代码,此时恰好线程2也执行到if(singleton == null)返回true,那么两个线程都会去new Singleton(),这就生成了两个实例,违背了我们的单例模式特点,这种问题在多线程模式下几率非常大,不容忽视。
线程安全的单例模式
最简单的方法就是在获取实例的静态方法上加synchronized锁,这样就不会有多个线程同时访问这个方法,也就不会有多线程问题了。这种方法解决解决问题虽然简单,不过在多线程访问下,性能很低,线程之间频繁等待,不是很好的解决方法。
还有一种简单的解决方法,利用静态特性来实现单例模式
public class SingletonTest {     private SingletonTest() {     }     private static final SingletonTest instance = new SingletonTest();     public static SingletonTest getInstancei() {         return instance ;     } }

这段代码实现起来也很简单,且没有多线程和性能问题,唯一的问题是这个单例的实例会在还没有使用的时候就被加载到内存,而不是等到使用的时候才实例化。不过我认为这个实现方式还是挺方便的。这种也叫饿汉式实现方式。
使用二次检查锁的方式实现单例模式:
public class Singleton {     private Singleton(){}     private static volatile Singleton singleton;     public static Singleton getInstance(){           if(singleton == null){               synchronized(Singleton.class){                    if(singleton == null){                         singleton = new Singleton();                   }              }          }           return singleton ;     }}

简单解释下上面的代码,我们注意到有一个明显不同之处在于,singleton实例使用了volatile关键字修饰,我们知道volatile有两个关键作用,可见性和避免重排序,之前我一直以为这里的volatile起到的是可见性作用,其实不然,仔细看上述代码,在new Singleton()之前,我们已经加了synchronized修饰了,这就足够保证可见性了,所以这里volatile起到的是避免重排序的作用,因为new Singleton()这段代码可能会被重排序,导致对象引用虽然生成了,实例却没有真的生成,这就可能导致直接使用的时候会报空指针异常(概率很低),我们还是要防范这个问题。
二次检查锁在多线程条件下不会有严重的性能问题,因为synchronized修饰的代码只会在第一次对象没有实例化时调用一次,后续就不会再调用了,所以多线程调用不会阻塞。
还有一种通过静态内部类来实现的单例模式,也是很好的实现方式
public class Singleton {       private static class LazyHolder {          private static final Singleton INSTANCE = new Singleton();       }       private Singleton (){}       public static final Singleton getInstance() {          return LazyHolder. INSTANCE;       }   }  

这种方式也是很好的实现方式,即能保证线程安全,同时也是在每次使用的时候才会实例化对象。

看到Effective Java上推荐的用枚举来实现单例模式,看起来更加简单方便,而且也能解决多线程等问题,代码却只要两行而已
public enum SingletonEnum {     INSTANCE}
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过
总结:
其实单例模式的写法还是挺多的,也经常看到什么"单例模式的七种写法"这种文章,其实没有哪种方法就是绝对好的,我们只要保证多线程下的安全性及性能,具体用哪种方法还是看场景和喜好吧,一般来说二次检查锁的方式是比较好的实现方式,推荐使用这种方式。枚举实现单例模式是Effective Java中推荐的实现方式,代码简洁安全,也值得推荐。
0 0