设计模式之单例模式(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.优点
- 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
- 由于单例模式只申城一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其它依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决(在JavaEE中采用单例模式时需要注意JVM垃圾回收机制)
- 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
2.缺点
- 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。单例模式为什么不能提供接口呢?因为接口对单例模式没有任何意义的,它要求“自行实例化”,并且提供单一示例、接口或抽象类是不可能被实例化的。当然,在特殊情况下单例模式可以实现接口、被继承等,需要在系统开发中根据环境判断。
- 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
- 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,并不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。
3.单例模式的使用场景
- 要求生成唯一序列号的环境
- 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的
- 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)
三、实现
传统的Singleton的写法
- 通过私有化构造器,将单例公有化。
- 私有化单例提供一个工友函数来返回静态单例
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.懒汉与饿汉区别
- 饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不在改变。懒汉式如果在创建实例对象时不加上synchronized则会导致对对象的访问不是线程安全的。
- 从实现方式来讲他们最大的区别就是懒汉式是延时加载,他是在需要的时候才创建对象,而饿汉式在虚拟机启动的时候就会创建,饿汉式无需关注多线程问题、写法简单明了、能用则用。但是它是加载类时创建实例、所以如果是一个工厂模式、缓存了很多实例、那么就得考虑效率问题,因为这个类一加载则把所有实例不管用不用一块创建。
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.注意事项
- 如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
- 如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
- 在高并发情况下,一定要注意单例模式的线程同步问题,特别是饿汉模式。
- 对象不知情况。在Java中,对象默认是不可以被复制的,若实现了Cloneable接口,并且实现了clone方法,则可以直接通过对象复制方式创建一个对象,对象复制是不用调用类的构造函数,因此即使是私有的构造函数,对象仍然可以被复制。这种情况很少见,但是还是提出来比较好,解决该问题的最好方法是单例类不实现Cloneable接口。
- 注意JVM的垃圾回收机制,如果我们的一个单例对象在内存中长久不使用,JVM就认为这个对象是一个垃圾,在CPU资源空闲的情况下该对象会被清理掉,下次再调用时就需要重新产生一个队形。如果我们在应用中使用单例类作为有状态值(如计数器)的管理,则会出现恢复原状的情况,应用就会出现故障。
参考资源:
《设计模式之禅》
《Effective Java中文版 第二版》
单例模式的七种写法
********************************************************************************结束语********************************************************************************************
我在写这篇博客的时候也是一名初学者,有任何疑问或问题请留言,或发邮件也可以,邮箱为:577328725@qq.com,我会尽早的进行更正及更改。
在我写过的博客中有两篇博客是对资源的整理,可能对大家都有帮助,大家有兴趣的话可以看看!!
下载资料整理——目录:http://blog.csdn.net/fanxiaobin577328725/article/details/51894331
这篇博客里面是我关于我见到的感觉不错的好资源的整理,里面包含了书籍及源代码以及个人搜索的一些资源,如果有兴趣的可以看看,我会一直对其进行更新和添加。
优秀的文章&优秀的学习网站之收集手册:http://blog.csdn.net/fanxiaobin577328725/article/details/52753638
这篇博客里面是我对于我读过的,并且感觉有意义的文章的收集整理,纯粹的个人爱好,大家感觉有兴趣的可以阅读一下,我也会时常的对其进行更新。
********************************************************************************感谢********************************************************************************************
- 设计模式之单例模式(Java1.5新特性)
- JDK1.5新特性、单例设计模式
- JAVA1.5新特性----享元模式:flayweight
- Java1.8新特性中的观察者模式
- 【黑马程序员】java1.5新特性-----可变参数,静态导入,高级for循环及享元设计模式
- 设计模式之(单例模式)
- 设计模式之(单例模式)
- 设计模式之单例设计模式(饿汉单例设计模式&懒汉单例设计模式)
- java1.5高新特性之枚举(1)
- java1.5新特性之枚举
- java1.5新特性之枚举总结
- Java1.5新特性之注解
- Java1.5新特性之泛型
- Java1.5新特性
- Java1.5新特性
- Java1.5新特性
- Java1.5新特性
- java1.5新特性
- CentOS7 安装Mysql5.7(解压缩版)
- 欢迎使用CSDN-markdown编辑器
- Android简易实战教程--第三十一话《自定义土司》
- 古老的邮件编码
- cf#373div1-C
- 设计模式之单例模式(Java1.5新特性)
- SQL 数据库 学习 004 预备知识
- leetcode 401 Binary Watch
- 文档元数据
- HTML+CSS基础 table标签border属性设置表格最外部的线条的粗细
- MyBatis 学习笔记
- Java编程之猴子吃桃
- JAVA设计模式
- Android控件Spinner实现下拉列表测试代码