24种设计模式之单例模式
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种设计模式之单例模式相关推荐
- [转自左潇龙的博客]设计模式大杂烩(24种设计模式的总结以及学习设计模式的几点建议)...
原文地址: https://www.cnblogs.com/zuoxiaolong/p/pattern26.html 作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本 ...
- 24种设计模式(一)
24种设计模式 第1章 简单工厂 第2章 外观模式 第3章 适配器模式 第4章 单例模式 第5章 工厂方法模式 第6章 抽象工厂模式 第7章 生成器模式 第8章 原型模式 第9章 中介者模式 第10章 ...
- 设计模式大杂烩(24种设计模式的总结及学习设计模式的几点建议)
设计模式大杂烩(24种设计模式的总结及学习设计模式的几点建议)模式分类 & 传送门 & 对比维度说明 设计原则: 设计模式(总纲) 创建型: 单例模式 简单工厂模式 工厂方法模式 抽象 ...
- Python七大原则,24种设计模式
七大设计原则: 1.单一职责原则[SINGLE RESPONSIBILITY PRINCIPLE]:一个类负责一项职责. 2.里氏替换原则[LISKOV SUBSTITUTION PRINCIPLE ...
- java 23种设计模式 04 单例模式
java 23种设计模式 04 单例模式 一.什么是单例模式 单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的实例对象.也就是说,在整个程序空间中,该类只存在一个实例对象. ...
- DP:***24种设计模式--转自刘伟
转自于高人的文章:http://blog.csdn.net/lovelion/article/details/17517213 2012年-2013年,Sunny在CSDN技术博客中陆续发表了100多 ...
- Java的二十三种设计模式(单例模式、工厂方法模式、抽象工厂模式)
从这一块开始,我们详细介绍Java中23种设计模式的概念,应用场景等情况,并结合他们的特点及设计模式的原则进行分析. 创建型模式(5种):用于描述"怎样创建对象",它的主要特点是& ...
- 23种设计模式之单例模式、工厂模式、原型模式、建造者模式
系列文章目录 第一章:程序设计原则-单一职责.接口隔离.依赖倒置.里式替换 第二章:程序设计原则-开闭原则.迪米特法则.合成复用原则 文章目录 系列文章目录 一.设计模式简单介绍 1.1.什么是设计模 ...
- 24种设计模式及案例
真诚的,TNANKS. 个人Github-24种设计模式案例链接 个人Github-24种设计模式案例链接 创建型模式 工厂模式 介绍 抽象工厂模式 介绍 单例模式 介绍 建造者模(构建者模式) 介绍 ...
最新文章
- LeetCode简单题之只出现一次的数字
- 28岁硕士女程序员想分手!对象专科学历,北京土著,失业3个月找不到工作!遭网友群嘲!...
- 极客编程日历桌面版for mac开发笔记[swift]
- Boost:字符串查找子串测试实例
- freemarker跳出循环
- C#并发编程之异步编程(二)
- cc java开发环境搭建_Windows系统下java开发环境搭建
- PS教程第九课:背景色
- java连接sqlserver报错
- EasyExcel项目使用
- 秋式开源团队,欢迎您的加入!
- mysql中文乱码 go_Mysql binlog乱码问题研究-Go语言中文社区
- NSRegularExpression iOS自带的正则表达式
- 【Oracle】手工建库时启动到nomount状态时错误ORA-09925,ORA-01017
- 多臂老虎机导论(二)Stochastic Bandits
- ftp关闭mysql约束校验_使用mysql验证配置ftp服务器
- dell5580bios恢复出厂_dell电脑恢复BIOS默认设置教程
- 如何向github上传代码
- django登录注册html页面,Django实现页面注册登录界面
- mysql生成类似qq号_【mysql】类似QQ的好友关系表是怎么设计的?
热门文章
- Netflix Conductor 快速入门
- 发布“豪情”设计的新博客皮肤-darkgreentrip
- JVM运行时数据区之Java堆
- c++11之std::move函数
- win11如何开启管理员账户
- System.ComponentModel.Win32Exception (0x80004005): 无效的窗口句柄。
- 谁养鱼问题的思路分析
- socket read
- mysql-connector-net 连接器下载
- Kali与Shell编程 -- 软件包管理命令deb安装