单例模式你会几种写法?
前言
只有光头才能变强
回顾前面:
- 给女朋友讲解什么是代理模式
- 包装模式就是这么简单啦
本来打算没那么快更新的,这阵子在刷Spring的书籍。在看Spring的时候又经常会看到“单例”,“工厂”这些字样。
所以,就先来说说单例和工厂设计模式啦,这两种模式也是很常见的,我看很多面经都会遇到这两种模式~
本文主要讲解单例设计模式,如果有错的地方希望能多多包涵,并不吝在评论区指正!
一、单例模式概述
单例模式定义很简单:一个类中能创建一个实例,所以称之为单例!
那我们什么时候会用到单例模式呢??
- 那我们想想既然一个类中只能创建一个实例了,那么可以说这是跟类的状态与对象无关的了。
- 频繁创建对象、管理对象是一件耗费资源的事,我们只需要创建一个对象来用就足够了!
学过Java Web的同学可能就知道:
- Servlet是单例的
- Struts2是多例的
- SpringMVC是单例的
那既然多例是频繁创建对象、需要管理对象的,那Struts2为什么要多例呢??
- 主要由于设计层面上的问题,Struts2是基于Filter拦截类的,ognl引擎对变量是注入的。所以它要设计成多例的~
能使用一个对象来做就不用实例化多个对象!这就能减少我们空间和内存的开销~
那有可能有的人又会想了:我们使用静态类.doSomething()
和使用单例对象调用方法的效果是一样的啊。
- 没错,效果就是一样的。使用
静态类.doSomething()
体现的是基于对象,而使用单例设计模式体现的是面向对象。
二、编写单例模式的代码
编写单例模式的代码其实很简单,就分了三步:
- 将构造函数私有化
- 在类的内部创建实例
- 提供获取唯一实例的方法
2.1饿汉式
根据上面的步骤,我们就可以轻松完成创建单例对象了。
public class Java3y {// 1.将构造函数私有化,不可以通过new的方式来创建对象private Java3y(){}// 2.在类的内部创建自行实例private static Java3y java3y = new Java3y();// 3.提供获取唯一实例的方法public static Student getJava3y() {return java3y;}
}
这种代码我们称之为:“饿汉式”:
- 一上来就创建对象了,如果该实例从始至终都没被使用过,则会造成内存浪费。
2.2简单懒汉式
既然说一上来就创建对象,如果没有用过会造成内存浪费:
- 那么我们就设计用到的时候再创建对象!
public class Java3y {// 1.将构造函数私有化,不可以通过new的方式来创建对象private Java3y(){}// 2.1先不创建对象,等用到的时候再创建private static Java3y java3y = null;// 2.1调用到这个方法了,证明是要被用到的了public static Java3y getJava3y() {// 3. 如果这个对象引用为null,我们就创建并返回出去if (java3y == null) {java3y = new Java3y();}return java3y;}
}
上面的代码行不行??在单线程环境下是行的,在多线程环境下就不行了!
- 如果不知道为啥在多线程环境下不行的同学可参考我之前的博文:多线程基础必要知识点!看了学习多线程事半功倍
要解决也很简单,我们只要加锁就行了:
2.3双重检测机制(DCL)懒汉式
上面那种直接在方法上加锁的方式其实不够好,因为在方法上加了内置锁在多线程环境下性能会比较低下,所以我们可以将锁的范围缩小。
public class Java3y {private Java3y() {}private static Java3y java3y = null;public static Java3y getJava3y() {if (java3y == null) {// 将锁的范围缩小,提高性能synchronized (Java3y.class) {java3y = new Java3y();}}return java3y;}
}
那上面的代码可行吗??不行,因为虽然加了锁,但还是有可能创建出两个对象出来的:
- 线程A和线程B同时调用
getJava3y()
方法,他们同时判断java==null
,得出的结果都是为null,所以进入了if代码块了 - 此时线程A得到CPU的控制权–>进入同步代码块–>创建对象–>返回对象
- 线程A完成了以后,此时线程B得到了CPU的控制权。同样是–>进入同步代码块–>创建对象–>返回对象
- 很明显的是:Java3y类返回了不止一个实例!所以上面的代码是不行的!
有的同学可能觉得我瞎吹比,明明加锁了还不行?我们来测试一下:
public class TestDemo {public static void main(String[] args) {// 线程Anew Thread(() -> {// 创建单例对象Java3y java3y = Java3y.getJava3y();System.out.println(java3y);}).start();// 线程Bnew Thread(() -> {// 创建单例对象Java3y java3y = Java3y.getJava3y();System.out.println(java3y);}).start();// 线程Cnew Thread(() -> {// 创建单例对象Java3y java3y = Java3y.getJava3y();System.out.println(java3y);}).start();}
}
可以看到,打印出的对象不单单只有一个的!
厉害的程序员又想到了:进入同步代码块时再判断一下对象是否存在就稳了吧!
- 所以,有了下面的代码
public class Java3y {private Java3y() {}private static Java3y java3y = null;public static Java3y getJava3y() {if (java3y == null) {// 将锁的范围缩小,提高性能synchronized (Java3y.class) {// 再判断一次是否为nullif (java3y == null) {java3y = new Java3y();}}}return java3y;}
}
其实还不稳!这里会有重排序的问题:
本来想测试重排序问题的效果的,一直没测试出来~~~有相关测试代码的希望可以告诉我怎么能测出来….
要解决也十分简单,加上我们的volatile关键字就可以了,volatile有内存屏障的功能!
具体可参考资料:
- https://www.zhihu.com/question/35268028—-双重检查锁失效是因为对象的初始化并非原子操作?
- http://ifeve.com/doublecheckedlocking/—有关“双重检查锁定失效”的说明
- https://my.oschina.net/u/866190/blog/205454—-正确使用双重检查锁(DCL)
所以说,完整的DCL代码是这样子的:
public class Java3y {private Java3y() {}private static volatile Java3y java3y = null;public static Java3y getJava3y() {if (java3y == null) {// 将锁的范围缩小,提高性能synchronized (Java3y.class) {// 再判断一次是否为nullif (java3y == null) {java3y = new Java3y();}}}return java3y;}
}
再说明:
2.4静态内部类懒汉式
还可以使用静态内部类这种巧妙的方式来实现单例模式!它的原理是这样的:
- 当任何一个线程第一次调用
getInstance()
时,都会使SingletonHolder被加载和被初始化,此时静态初始化器将执行Singleton的初始化操作。(被调用时才进行初始化!) - 初始化静态数据时,Java提供了的线程安全性保证。(所以不需要任何的同步)
public class Java3y {private Java3y() {}// 使用内部类的方式来实现懒加载private static class LazyHolder {// 创建单例对象private static final Java3y INSTANCE = new Java3y();}// 获取对象public static final Java3y getInstance() {return LazyHolder.INSTANCE;}}
静态内部类这种方式是非常推荐使用的!很多人没接触过单例模式的都不知道有这种写法,这种写法很优化也高效!
参考资料:
- https://www.zhihu.com/question/35454510/answer/62829602—-java 单例模式通过内部静态类的方式?
2.5枚举方式实现
使用枚举就非常简单了:
public enum Java3y3y {JAVA_3_Y_3_Y,
}
那这种有啥好处??枚举的方式实现:
- 简单,直接写就行了
- 防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候(安全)!
这种也较为推荐使用!
三、总结
总的来说单例模式写法有5种:
- 饿汉式
- 简单懒汉式(在方法加锁)
- DCL双重检测加锁(进阶懒汉式)
- 静态内部类实现懒汉式(最推荐写法)
- 枚举方式(最安全、简洁写法)
明天估计写的是工厂模式了,敬请期待哦~~~
参考资料:
- 《设计模式之禅》
- http://www.cnblogs.com/seesea125/archive/2012/04/05/2433463.html—为什么要用单例模式?
- https://zhuanlan.zhihu.com/p/32310340—圣诞节,让我们聊聊单例模式
- https://zhuanlan.zhihu.com/p/34406410—单例模式详解
- http://www.nowamagic.net/librarys/veda/detail/1776—使用单例模式需要注意的几个问题
- https://zhuanlan.zhihu.com/p/23713957—Java设计模式(一)-单例模式
如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:Java3y。为了大家方便,刚新建了一下qq群:742919422,大家也可以去交流交流。谢谢支持了!希望能多介绍给其他有需要的朋友
文章的目录导航:
- https://zhongfucheng.bitcron.com/post/shou-ji/wen-zhang-dao-hang
单例模式你会几种写法?相关推荐
- 单例模式Java的七种写法
2019独角兽企业重金招聘Python工程师标准>>> 单例模式算是开发中使用比较多的设计模式,Hibernate的SessionFactory.Spring的Application ...
- KandQ:单例模式的七种写法及其相关问题解析
设计模式中的单例模式可以有7种写法,这7种写法有各自的优点和缺点: 代码示例(java)及其分析如下: 一.懒汉式 public class Singleton {private static Sin ...
- java 静态内部类 线程安全问题_单例模式的七种写法, 面试题:线程安全的单例模式...
http://cantellow.iteye.com/blog/838473 http://meizhi.iteye.com/blog/537563 第一种(懒汉,线程不安全): Java代码 pu ...
- Java设计模式——单例模式的七种写法
单例模式(Singleton) 单例模式(Singleton)是一种常用的设计模式.在Java应用中,单例模式能保证在一个JVM中,该对象只有一个实例存在.这样的模式有几个好处: 1.某些类创建比较频 ...
- 单例模式的七种写法(转)
转载请注明出处:http://cantellow.iteye.com/blog/838473 第一种(懒汉,线程不安全): Java代码 public class Singleton { priv ...
- java单例模式的七种写法_Java设计模式之单例模式的七种写法
什么是单例模式? 单例模式是一种常见的设计模式,单例模式的写法有很多种,这里主要介绍三种: 懒汉式单例模式.饿汉式单例模式.登记式单例 . 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类 ...
- 单例模式的八种写法比较
转:https://www.cnblogs.com/zhaoyan001/p/6365064.html 单例模式是最常用到的设计模式之一,熟悉设计模式的朋友对单例模式都不会陌生.一般介绍单例模式的书籍 ...
- Java单例模式的七种写法
第一种(懒汉,线程不安全): public class Singleton { private static Singleton instance; private Singleton (){} pu ...
- 【设计模式】各个击破单例模式的8种写法
单例模式 在一个系统开发过程中,我们在基于节省内存资源.保证数据内容的一致性的考虑上,往往需要对某些类要求只能创建一个实例,即「保证类只有一个实例」的设计模式就是单例模式. 比如我们遇到过的各种Man ...
最新文章
- bat贪吃蛇游戏代码_C语言写个贪吃蛇游戏
- MySQL数据类型和Java数据类型对应关系表
- Qt Example各例子演示功能说明
- 用随机数发生器射击自己的脚
- c语言中 快速输出字符数组后几位方法
- 网络存储(三)之ISCSI搭建的入门
- oracle锁表与解表
- 中国计算机学会暂停与IEEE合作;百度接连五位高管离职;所有版本 Docker 被爆严重漏洞!| 极客头条...
- 8选1的多路选择器c语言代码,八选一数据选择器的VHDL程序
- 《thor过滤器 thor过滤规则合集资源》500+
- CATIA如何实现设计模块快速切换?
- “华为”云桌面终端CT3200+显示器、键盘和鼠标
- python列表逐个输出_python的列表元素输出
- 易腐食品行业调研报告 - 市场现状分析与发展前景预测
- 离散数学笔记(期末复习用,持续更新…)
- Phalcon框架的访问控制列表 ACL(Access Control Lists ACL)
- 解决移动端滑动方向相反
- nodeJS打包安装和问题处理
- [附源码]java毕业设计体检中心健康管理系统
- 聚合支付码是什么意思及它产生的背景