设计模式之单例模式(Java1.5新特性)

来源:互联网 发布:韩国导演排名知乎 编辑:程序博客网 时间:2024/05/17 01:19

一、概念

    单例模式 (Singleton pattern ):确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。(Ensure a class has only one instance,and provide a global point of access to it.)

     单例模式是所有设计模式中比较简单的一个。

二、单例模式的优缺点

1.优点

  1. 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
  2. 由于单例模式只申城一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其它依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决(在JavaEE中采用单例模式时需要注意JVM垃圾回收机制)
  3. 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
  4. 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

2.缺点

  1. 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。单例模式为什么不能提供接口呢?因为接口对单例模式没有任何意义的,它要求“自行实例化”,并且提供单一示例、接口或抽象类是不可能被实例化的。当然,在特殊情况下单例模式可以实现接口、被继承等,需要在系统开发中根据环境判断。
  2. 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
  3. 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,并不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。

3.单例模式的使用场景

  1. 要求生成唯一序列号的环境
  2. 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的
  3. 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源
  4. 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)

三、实现

传统的Singleton的写法

  1. 通过私有化构造器,将单例公有化。
  2. 私有化单例提供一个工友函数来返回静态单例

1.懒汉模式

     顾名思义:在生成单一类的实例时延迟加载,在第一次使用时(调用)才进行创建实例。

public class SingleTon {private static SingleTon  INSTANCE = null;private SingleTon(){}public static SingleTon getInstance(){//返回实例的时候检查是否创建if(null==INSTANCE){INSTANCE=new SingleTon();}return INSTANCE;}}

上面的实例完全实现了单例模式,但是去油致命的缺陷,多线程是不安全的,下面是上面实例的优化:

public class SingleTon {private static SingleTon  INSTANCE = null;private SingleTon(){}public static synchronized SingleTon getInstance(){//返回实例的时候检查是否创建if(null==INSTANCE){INSTANCE=new SingleTon();}return INSTANCE;}}
这种写法能够在多线程中很好的工作,但是,遗憾的是,效率很低,99%情况下不需要同步。
为了更清楚的显示器运行过程,我们对上述代码进行了改造:

public class SingleTon {private static SingleTon  INSTANCE=null;private SingleTon(){System.out.println("3==>调用构造函数进行初始化");}public static synchronized SingleTon getInstance(){System.out.println("1==>调用获取实例函数");//返回实例的时候检查是否创建if(null==INSTANCE){System.out.println("2==>实例并没有初始化");INSTANCE=new SingleTon();}System.out.println("4==>返回生成实例");return INSTANCE;}}
输出结果为:

1==>调用获取实例函数2==>实例并没有初始化3==>调用构造函数进行初始化4==>返回生成实例

升级版-双重检查锁定(1.5之后的新特性)

public class SingleTon {private volatile static SingleTon singleton;private SingleTon() {}public static SingleTon getSingleton() {if (null == singleton) {synchronized (SingleTon.class) {if (null == singleton) {singleton = new SingleTon();}}}return singleton;}}

2.饿汉模式

     顾名思义:提前将实例创建好,无论是否需要调用。

public class SingleTon {private static SingleTon INSTANCE = new SingleTon();private SingleTon() {}public static synchronized SingleTon getInstance() {return INSTANCE;}}
这种方式基于加载机制避免了多线程的同步问题,INSTANCE在类装载时就实例化了。还有可以使用静态代码块,以及构造块来实现。

简单叙述:static{...}是静态块,而只有{...}的是叫做构造块。

  • 静态块在一个程序里面只执行一次;而构造块是,只要建立一个对象,构造代码块都会执行一次。
  • 静态块优先于主方法的执行,静态块优先于构造快,然后是构造方法的执行,而且只执行一次!

这里还是 详细叙述一下另一种特殊的饿汉模式:静态内部类

public class SingleTon {private static class SingletonHolder {private static SingleTon INSTANCE = new SingleTon();}private SingleTon() {}public static synchronized SingleTon getInstance() {return SingletonHolder.INSTANCE;}}
看起来没有太大区别,但是还是加载方式上的细微区别。前面的几种方式只要Singleton类被装载了,那么instance就会被实例化,而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,静态内部类的方式就显得很合理了。

3.懒汉与饿汉区别

  1. 饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不在改变。懒汉式如果在创建实例对象时不加上synchronized则会导致对对象的访问不是线程安全的。
  2. 从实现方式来讲他们最大的区别就是懒汉式是延时加载,他是在需要的时候才创建对象,而饿汉式在虚拟机启动的时候就会创建,饿汉式无需关注多线程问题、写法简单明了、能用则用。但是它是加载类时创建实例、所以如果是一个工厂模式、缓存了很多实例、那么就得考虑效率问题,因为这个类一加载则把所有实例不管用不用一块创建。

4.懒汉与饿汉缺陷

这两种方式都可以使用反射来生成新的对象。

单例代码:

public class SingleTon {public final static SingleTon INSTANCE = new SingleTon();private SingleTon() {}}
测试代码:

public class Ceshi_Singleton {public static void main(String[] args) {try {SingleTon s1=SingleTon.INSTANCE;Class<SingleTon> cls=SingleTon.class;Constructor<SingleTon> constructor=cls.getDeclaredConstructor(new Class[]{});constructor.setAccessible(true);SingleTon s2=constructor.newInstance(new Object[]{});System.out.println(s1==s2);} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {e.printStackTrace();}}}
输出结果:

false
对于这个结果很显然证明了这两个实例不是同一,对于单例模式,是否很诧异呢?

5.新特性之枚举

    这是JDK1.5所推出的一个新特性,这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅更加简洁同时能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候。虽然这种方法还没有广泛使用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。可以参考《Effective Java 第二版》的第三条——用私有构造器或者枚举类型强化Singleton属性。

public enum SingleTon {INSTANCE;//...}
就这么简单,而且很强悍。具体他是属于懒汉还是饿汉,暂时不明白。

6.注意事项

  1. 如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
  2. 如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
  3. 在高并发情况下,一定要注意单例模式的线程同步问题,特别是饿汉模式。
  4. 对象不知情况。在Java中,对象默认是不可以被复制的,若实现了Cloneable接口,并且实现了clone方法,则可以直接通过对象复制方式创建一个对象,对象复制是不用调用类的构造函数,因此即使是私有的构造函数,对象仍然可以被复制。这种情况很少见,但是还是提出来比较好,解决该问题的最好方法是单例类不实现Cloneable接口。
  5. 注意JVM的垃圾回收机制,如果我们的一个单例对象在内存中长久不使用,JVM就认为这个对象是一个垃圾,在CPU资源空闲的情况下该对象会被清理掉,下次再调用时就需要重新产生一个队形。如果我们在应用中使用单例类作为有状态值(如计数器)的管理,则会出现恢复原状的情况,应用就会出现故障。


参考资源:

《设计模式之禅》

《Effective Java中文版 第二版》

单例模式的七种写法


********************************************************************************结束语********************************************************************************************
  我在写这篇博客的时候也是一名初学者,有任何疑问或问题请留言,或发邮件也可以,邮箱为:577328725@qq.com,我会尽早的进行更正及更改。
在我写过的博客中有两篇博客是对资源的整理,可能对大家都有帮助,大家有兴趣的话可以看看!!
下载资料整理——目录:http://blog.csdn.net/fanxiaobin577328725/article/details/51894331
  这篇博客里面是我关于我见到的感觉不错的好资源的整理,里面包含了书籍及源代码以及个人搜索的一些资源,如果有兴趣的可以看看,我会一直对其进行更新和添加。
优秀的文章&优秀的学习网站之收集手册:http://blog.csdn.net/fanxiaobin577328725/article/details/52753638
  这篇博客里面是我对于我读过的,并且感觉有意义的文章的收集整理,纯粹的个人爱好,大家感觉有兴趣的可以阅读一下,我也会时常的对其进行更新。
********************************************************************************感谢********************************************************************************************

0 0
原创粉丝点击