24种设计模式之单例模式 

一、核心作用

保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。

二、常见的应用场景

1、Windows的Task Manager(任务管理器)就是很典型的单例模式;

2、windows的Recycle Bin(回收站)也是典型的单例应用.在整个系统运行过程中,回收站一直维护着仅有的一个实例。

3、项目中,读取配置文件的类,一般也只有一对象。没有必要每次使用配置文件数据,每次new一个又对象去读取。

4、网站的计数器,一般也是采用单例模式实现,否则难以同步。

5、应用程序的日志应用,一般都可用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。

6、数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。

7、操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有1个文件系统。

8、Application也是单例的典型应用(Servlet编程中会涉及到)

9、在Spring中,每个Bean默认、就是单例的,这样做的优点是Spring容器可以管理

10、在Servlet编程中,每个Servlet也是单例

11、在SpringMVC框架/strutsl框架中,控制器对象也是单例。

三、单例模式的优点:

由于单例模式只生成一个实例,减少了系统性能开销,产生其他依赖对象时,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖烛象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。

单例模式可以在系统设置全局的访问点,优化并共享资源访问,例如可以设计一个单例类,负责所有数据表的映身寸处理。

四、常见的五种单例实现方式:

1.主要:

饿汉式(线程安全,调用效率高。但是,不能延时加载。)

懒汉式(线程安全,调用效率不高。但是,可以延时加载。)

2.其它:

双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)

静态内部类式(线程安全,调用效率高。可以延时加载)

枚举单例(线程安全,调用效率高,不能延时加载。并且可以天然的防止反射和反序列化漏洞!)

五、代码演示

1.饿汉式

/*** 测试饿汉式单例模式**/
public class SingletonDemo01 {//类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!private static /* final */ SingletonDemo01 instance = new SingletonDemo01();private SingletonDemo01() {}// 私有化构造器//方法没有同步,调用效率高!public static /* synchronized */SingletonDemo01 getInstance() {return instance;}
}

饿汉式单例模式代码中,static变量会在类加载时初始化,此时也不会涉及多个线程访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字。

问题:如果只是加载本类,而不是调用getInstance(),甚至永远没有调用,则会造成资源浪费!

2.懒汉式

public class SingletonDemo02 {//类初始化时,不初始化对象(延时加载,真正用的时候再创建)private static SingletonDemo02 instance;private SingletonDemo02() {}// 私有化构造器//方法同步,调用效率低!public static synchronized SingletonDemo02 getInstance() {if (null == instance) {instance = new SingletonDemo02();}return instance;}
}

要点:lazy load! 延迟加载,懒加载!真正用的时候才加载!

问题:资源利用率高了。但是,每次调用的getInstance()方法都要同步,并发效率较低。

3.双重检测锁式

/*** 双重检查实现单例模式**/
public class SingletonDemo03 {// 实例化对象的那行代码instance = new SingletonDemo03(),实际上可以分解成以下三个步骤:// 1.分配内存空间// 2.初始化对象// 3.将对象指向刚分配的内存空间// 但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:// 1.分配内存空间// 2.将对象指向刚分配的内存空间// 3.初始化对象// 即:多线程访问的时候,会出现这种错误状况:// 线程A检查instance为空,然后为instance分配内存空间,然后将instance指向内存空间。此时instance不为空,instance还未完成初始化,即未写(write),// 线程B也开始检查instance为非空,访问到的instance还未初始化异常信息。// 使用了volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。private volatile static SingletonDemo03 instance = null;private SingletonDemo03() {}// 私有化构造器public static SingletonDemo03 getInstance() {if (null == instance) {// 检查到instance为空synchronized (SingletonDemo03.class) {// 获取锁if (null == instance) {// 再次检查到instance为空// 1.为instance分配内存空间// 2.初始化instance对象// 3.将instance对象指向刚分配的内存空间instance = new SingletonDemo03();}}}return instance;}
}

这个模式将同步内容下方到if内部,提高了执行的效率。不必每次获取对象时都进行同步,只有第一次才同步创建了,以后就没有必要了。

问题:由于编译器优化原因和JVM底层内部模型原因,偶尔会出现问题。不建议使用

4.静态内部类式

/*** 静态内部类实现单例模式* 线程安全,调用效率高,懒加载**/
public class SingletonDemo4 {private SingletonDemo4() {}// 私有化构造器private static class SingletonClassInstance{private static final SingletonDemo4 instance = new SingletonDemo4();}public static SingletonDemo4 getInstance() {return SingletonClassInstance.instance;}
}

要点:外部类没有static属性,则不会像饿汉式那样立即加载对象。只有真正调用getInstance();才会加载静态内部类,加载类时是线程安全的。instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。兼备了并发高效调用和延迟加载的优势!

5.枚举单例

/*** 枚举实现单例模式 线程安全,调用效率高,无延迟加载**/
public enum SingletonDemo5 {/*** 这个枚举元素,本身就是单例对象*/INSTANCE;/*** 单例可以有自己的操作*/public void SingletonOperation() {// 功能处理}
}

优点:实现简单
枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!

缺点:无延迟加载

五、如何选用以上五种单例模式?

单例对象占用资源少,不需要延迟加载。枚举式好于恶汉式。

单例对象占用资源大,需要延迟加载。静态内部类式好于懒汉式。

六、单例模式的破解及防护:

1.反射破解(不包含枚举式)

 public static void main(String[] args) throws Exception {//通过反射方式直接调用私有构造器Class<SingletonDemo6> clazz=(Class<SingletonDemo6>) Class.forName("com.uwo9.creational.singleton.SingletonDemo6");Constructor<SingletonDemo6> c=clazz.getDeclaredConstructor(null);c.setAccessible(true);//跳过权限检查,访问私有的SingletonDemo6 s3=c.newInstance();SingletonDemo6 s4=c.newInstance();System.out.println(s3 == s4);//结果为false}

防护方法:可在构造方法中手动抛出异常控制。

public class SingletonDemo6 {private static SingletonDemo6 instance;private SingletonDemo6() throws Exception {if (instance != null) {throw new Exception("只能创建一个对象");// 通过手动抛出异常,避免通过反射创建多个单例对象!}}// 私有化构造器public static synchronized SingletonDemo6 getInstance() throws Exception {if (null == instance) {instance = new SingletonDemo6();}return instance;}}

2.反序列化破解(不包含枚举式)

 public static void main(String[] args) throws Exception {SingletonDemo6 s = SingletonDemo6.getInstance();//通过反序列化的方式构造多个对象。FileOutputStream fos=new FileOutputStream("d:/a.txt");ObjectOutputStream oos=new ObjectOutputStream(fos);oos.writeObject(s);oos.close();fos.close();ObjectInputStream ois=new ObjectInputStream(new FileInputStream("d:/a.txt"));SingletonDemo6 s5= (SingletonDemo6) ois.readObject();System.out.println(s == s5);//结果为false}

 防护方法:可以通过定义readResolve()防止获得不同对象。反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象。

public class SingletonDemo6 implements Serializable {private static SingletonDemo6 instance;private SingletonDemo6() throws Exception {if (instance != null) {throw new Exception("只能创建一个对象");// 通过手动抛出异常,避免通过反射创建多个单例对象!}}// 私有化构造器public static synchronized SingletonDemo6 getInstance() throws Exception {if (null == instance) {instance = new SingletonDemo6();}return instance;}//反序列化时,如果对象所在类定义了readResolve(),则直接返回此方法指定的对象。而不需要单独再创建新对象(实际是一种回调)private Object readResolve() throws ObjectStreamException{return instance;}
}

七、以上五种单例模式在多线程环境下的效率测试

只需关注相对值,不同环境下测试值不一样。

饿汉式 22ms
懒汉式 636ms
双重检索式式 65ms
静态内部类式 28ms
枚举式 32ms

此处上测试代码:

 public static void main(String[] args) throws InterruptedException {long start=System.currentTimeMillis();int threadNum=10;//同步辅助类,在完成一组正在其他线程中执行操作之前,它允许一个或多个线程一直等待。final CountDownLatch countDownLatch=new CountDownLatch(threadNum);for (int i = 0; i < 10; i++) {new Thread(new Runnable() {@Overridepublic void run() {for (int j = 0; j < 1000000; j++) {//此处调用单例//...省略Object o = SingletonDemo01.getInstance();}//当前线程调用此方法,则计数减一(建议放在finally里执行)countDownLatch.countDown();}}).start();}countDownLatch.await();//main线程阻塞,直到计数器变为0,才继续往下执行long end=System.currentTimeMillis();System.out.println("总耗时:"+(end-start));}

24种设计模式之单例模式相关推荐

  1. [转自左潇龙的博客]设计模式大杂烩(24种设计模式的总结以及学习设计模式的几点建议)...

    原文地址:    https://www.cnblogs.com/zuoxiaolong/p/pattern26.html 作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本 ...

  2. 24种设计模式(一)

    24种设计模式 第1章 简单工厂 第2章 外观模式 第3章 适配器模式 第4章 单例模式 第5章 工厂方法模式 第6章 抽象工厂模式 第7章 生成器模式 第8章 原型模式 第9章 中介者模式 第10章 ...

  3. 设计模式大杂烩(24种设计模式的总结及学习设计模式的几点建议)

    设计模式大杂烩(24种设计模式的总结及学习设计模式的几点建议)模式分类 & 传送门 & 对比维度说明 设计原则: 设计模式(总纲) 创建型: 单例模式 简单工厂模式 工厂方法模式 抽象 ...

  4. Python七大原则,24种设计模式

    七大设计原则: 1.单一职责原则[SINGLE RESPONSIBILITY PRINCIPLE]:一个类负责一项职责.  2.里氏替换原则[LISKOV SUBSTITUTION PRINCIPLE ...

  5. java 23种设计模式 04 单例模式

    java 23种设计模式 04 单例模式 一.什么是单例模式 单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的实例对象.也就是说,在整个程序空间中,该类只存在一个实例对象.   ...

  6. DP:***24种设计模式--转自刘伟

    转自于高人的文章:http://blog.csdn.net/lovelion/article/details/17517213 2012年-2013年,Sunny在CSDN技术博客中陆续发表了100多 ...

  7. Java的二十三种设计模式(单例模式、工厂方法模式、抽象工厂模式)

    从这一块开始,我们详细介绍Java中23种设计模式的概念,应用场景等情况,并结合他们的特点及设计模式的原则进行分析. 创建型模式(5种):用于描述"怎样创建对象",它的主要特点是& ...

  8. 23种设计模式之单例模式、工厂模式、原型模式、建造者模式

    系列文章目录 第一章:程序设计原则-单一职责.接口隔离.依赖倒置.里式替换 第二章:程序设计原则-开闭原则.迪米特法则.合成复用原则 文章目录 系列文章目录 一.设计模式简单介绍 1.1.什么是设计模 ...

  9. 24种设计模式及案例

    真诚的,TNANKS. 个人Github-24种设计模式案例链接 个人Github-24种设计模式案例链接 创建型模式 工厂模式 介绍 抽象工厂模式 介绍 单例模式 介绍 建造者模(构建者模式) 介绍 ...

最新文章

  1. LeetCode简单题之只出现一次的数字
  2. 28岁硕士女程序员想分手!对象专科学历,北京土著,失业3个月找不到工作!遭网友群嘲!...
  3. 极客编程日历桌面版for mac开发笔记[swift]
  4. Boost:字符串查找子串测试实例
  5. freemarker跳出循环
  6. C#并发编程之异步编程(二)
  7. cc java开发环境搭建_Windows系统下java开发环境搭建
  8. PS教程第九课:背景色
  9. java连接sqlserver报错
  10. EasyExcel项目使用
  11. 秋式开源团队,欢迎您的加入!
  12. mysql中文乱码 go_Mysql binlog乱码问题研究-Go语言中文社区
  13. NSRegularExpression iOS自带的正则表达式
  14. 【Oracle】手工建库时启动到nomount状态时错误ORA-09925,ORA-01017
  15. 多臂老虎机导论(二)Stochastic Bandits
  16. ftp关闭mysql约束校验_使用mysql验证配置ftp服务器
  17. dell5580bios恢复出厂_dell电脑恢复BIOS默认设置教程
  18. 如何向github上传代码
  19. django登录注册html页面,Django实现页面注册登录界面
  20. mysql生成类似qq号_【mysql】类似QQ的好友关系表是怎么设计的?

热门文章

  1. Netflix Conductor 快速入门
  2. 发布“豪情”设计的新博客皮肤-darkgreentrip
  3. JVM运行时数据区之Java堆
  4. c++11之std::move函数
  5. win11如何开启管理员账户
  6. System.ComponentModel.Win32Exception (0x80004005): 无效的窗口句柄。
  7. 谁养鱼问题的思路分析
  8. socket read
  9. mysql-connector-net 连接器下载
  10. Kali与Shell编程 -- 软件包管理命令deb安装