前言

只有光头才能变强

回顾前面:

  • 给女朋友讲解什么是代理模式
  • 包装模式就是这么简单啦

本来打算没那么快更新的,这阵子在刷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

单例模式你会几种写法?相关推荐

  1. 单例模式Java的七种写法

    2019独角兽企业重金招聘Python工程师标准>>> 单例模式算是开发中使用比较多的设计模式,Hibernate的SessionFactory.Spring的Application ...

  2. KandQ:单例模式的七种写法及其相关问题解析

    设计模式中的单例模式可以有7种写法,这7种写法有各自的优点和缺点: 代码示例(java)及其分析如下: 一.懒汉式 public class Singleton {private static Sin ...

  3. java 静态内部类 线程安全问题_单例模式的七种写法, 面试题:线程安全的单例模式...

    http://cantellow.iteye.com/blog/838473 http://meizhi.iteye.com/blog/537563 第一种(懒汉,线程不安全): Java代码  pu ...

  4. Java设计模式——单例模式的七种写法

    单例模式(Singleton) 单例模式(Singleton)是一种常用的设计模式.在Java应用中,单例模式能保证在一个JVM中,该对象只有一个实例存在.这样的模式有几个好处: 1.某些类创建比较频 ...

  5. 单例模式的七种写法(转)

    转载请注明出处:http://cantellow.iteye.com/blog/838473 第一种(懒汉,线程不安全): Java代码   public class Singleton { priv ...

  6. java单例模式的七种写法_Java设计模式之单例模式的七种写法

    什么是单例模式? 单例模式是一种常见的设计模式,单例模式的写法有很多种,这里主要介绍三种: 懒汉式单例模式.饿汉式单例模式.登记式单例 . 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类 ...

  7. 单例模式的八种写法比较

    转:https://www.cnblogs.com/zhaoyan001/p/6365064.html 单例模式是最常用到的设计模式之一,熟悉设计模式的朋友对单例模式都不会陌生.一般介绍单例模式的书籍 ...

  8. Java单例模式的七种写法

    第一种(懒汉,线程不安全): public class Singleton { private static Singleton instance; private Singleton (){} pu ...

  9. 【设计模式】各个击破单例模式的8种写法

    单例模式 在一个系统开发过程中,我们在基于节省内存资源.保证数据内容的一致性的考虑上,往往需要对某些类要求只能创建一个实例,即「保证类只有一个实例」的设计模式就是单例模式. 比如我们遇到过的各种Man ...

最新文章

  1. bat贪吃蛇游戏代码_C语言写个贪吃蛇游戏
  2. MySQL数据类型和Java数据类型对应关系表
  3. Qt Example各例子演示功能说明
  4. 用随机数发生器射击自己的脚
  5. c语言中 快速输出字符数组后几位方法
  6. 网络存储(三)之ISCSI搭建的入门
  7. oracle锁表与解表
  8. 中国计算机学会暂停与IEEE合作;百度接连五位高管离职;所有版本 Docker 被爆严重漏洞!| 极客头条...
  9. 8选1的多路选择器c语言代码,八选一数据选择器的VHDL程序
  10. 《thor过滤器 thor过滤规则合集资源》500+
  11. CATIA如何实现设计模块快速切换?
  12. “华为”云桌面终端CT3200+显示器、键盘和鼠标
  13. python列表逐个输出_python的列表元素输出
  14. 易腐食品行业调研报告 - 市场现状分析与发展前景预测
  15. 离散数学笔记(期末复习用,持续更新…)
  16. Phalcon框架的访问控制列表 ACL(Access Control Lists ACL)
  17. 解决移动端滑动方向相反
  18. nodeJS打包安装和问题处理
  19. [附源码]java毕业设计体检中心健康管理系统
  20. 聚合支付码是什么意思及它产生的背景

热门文章

  1. uni-app引入全局css样式
  2. 0R电阻和1R电阻的用处
  3. Spring Security 鉴权流程
  4. 基于CH340的一键下载电路
  5. 软考系统集成项目管理工程师视频教程(计算机系统集成部分)-乔俊峰-专题视频课程...
  6. 996程序员入职一年多,同事涨了4千他没涨,跟领导提涨薪,回复愣了
  7. 充分条件,必要条件,充分必要条件
  8. Windows下反(反)调试技术汇总
  9. IBM Power6、7配件FC号描述翻译(unix360.part06)
  10. linux时间变成英文,查看/修改Linux时区和时间