JAVA设计模式之单例模式

来源:互联网 发布:预科生的贩毒网络 编辑:程序博客网 时间:2024/05/06 08:22

JAVA设计模式之单例模式

概念:
  Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍5种:懒汉式单例、饿汉式单例、内部静态类实现、枚举实现、双重检测锁实现。
  单例模式有以下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。
  单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态。
1. 懒汉式单例
看有无synchronized?有,则线程安全,调用效率不高,差1个数量级,但是可以延迟加载
2. 饿汉式单例
线程安全,调用效率高,不能延迟加载
3. 内部静态类实现
线程安全,调用效率高,能延迟加载
4. 枚举实现
线程安全,调用效率高,不能延迟加载,可以防止反射和反序列化漏洞
5. 双重检测锁实现
jdk5之后,不会有出现多个实例的问题,

如何选用

单例对象 占用资源少,不需要延迟加载    枚举实现 好于 饿汉式单例对象 占用资源大,需要延迟加载    静态内部类 好于懒汉式 

常见使用

常见使用有以下地方
1. 网站计数器
2. 数据库连接池一般也是采用单例模式,因为数据库连接是一种数据库资源
3. 在Spring中,每个Bean默认是单例
4. 在servlet中,每个servlet也是单例
5. 在spring MVC框架/struct1中,控制器对象也是单例

第一种(懒汉式,线程不安全):

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

这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。

第二种(懒汉,线程安全):

public class SingletonDemo4 {    private SingletonDemo4() {        if (null != singleton) {            throw new RuntimeException();        }    }    private SingletonDemo4 singleton;    public synchronized SingletonDemo4 getIntance() {        if (null == singleton) {            singleton = new SingletonDemo4();        }        return singleton;    }} 

这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。

第三种(饿汉, 线程安全):

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

这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

第四种(静态内部类):

public class SingletonDemo2 {    private SingletonDemo2() {}    private static class SingletonHandler {        public static final SingletonDemo2 INSTANCE = new Singleton();    }    public SingletonDemo2 getInstance() {        return SingletonHandler.INSTANCE;    }}  

这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。

第五种(枚举):

public enum SingletonDemo1 {    INSTANCE,;    private Singleton singletonClass;    SingletonDemo1(){        singletonClass = new Singleton();    }    public Singleton getInstance() {        return singletonClass;    }    public static void main(String[] args) {        SingletonDemo1 instance = SingletonDemo1.INSTANCE;        Singleton singletonClass = instance.getInstance();    }}

这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。

第六种(双重校验锁):

public class SingletonDemo3 {    private SingletonDemo3() {}    private SingletonDemo3 singleton;    public SingletonDemo3 getInstance() {        if (singleton == null) {            synchronized (SingletonDemo3.class) {                if (singleton == null) {                    singleton = new SingletonDemo3();                }            }        }        return singleton;    }}

安全问题

关于序列化漏洞
以往的单例实现了序列化接口,那么就再也不能保持单例的状态了.因为readObject()方法一直返回一个新的对象.使用radResolve()来避免此情况发生。在readResolve直接返回实例。

    //readResolve to prevent another instance of Singleton    private Object readResolve(){        return INSTANCE;    }

关于反射
可以构造器中判断,如果实例已经存在,抛出异常

    private Singleton (){        if(null != INSTANCE){            throw new RuntimeException();        }    }  
0 0
原创粉丝点击