设计模式 | 单例模式

  • 1.核心作用
  • 2.常见场景
  • 3.单例模式的优点
  • 4.常见的五种单例模式实现方式
    • 4.1 饿汉式
    • 4.2 懒汉式
    • 4.3 DCL懒汉式(双重检测锁模式)
    • 4.4 静态内部类
    • 4.5 枚举
  • 5.五种实现单例模式的方式的对比

1.核心作用

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

2.常见场景

3.单例模式的优点

4.常见的五种单例模式实现方式

4.1 饿汉式

步骤:

  1. 私有化构造器
  2. 类初始化时,立即加载对象【不涉及线程安全问题】
  3. 提供获取该对象的方法【没有Synchronized,效率高】
// 恶汉式public class SingletonDemo1 {private SingletonDemo1(){};private static SingletonDemo1 instance= new SingletonDemo1();public static SingletonDemo1 GetInstance(){return instance;}
}class SingletonDemo1Test {public static void main(String[] args) {SingletonDemo1 instance1 = SingletonDemo1.GetInstance();SingletonDemo1 instance2 = SingletonDemo1.GetInstance();boolean b = instance1 == instance2;System.out.println(b);//输出:true}
}

优点: 线程安全,调用效率高

缺点: 不能延时加载,如果长时间不用,会造成资源浪费

解决方案: 懒汉式

4.2 懒汉式

步骤:

  1. 私有化构造器
  2. 类初始化时,不立即加载对象
  3. 提供获取该对象的方法,在该方法上用Synchronized关键字,解决多线程冲突问题
//懒汉式
public class SingletonDemo2 {//1. 私有化构造器private  SingletonDemo2(){};//2. 类初始化时,不立即加载对象private static SingletonDemo2 instance;//3. 提供获取该对象的方法,在该方法上用Synchronized关键字,解决多线程冲突问题public static synchronized SingletonDemo2 getInstance(){if(instance!=null){return new SingletonDemo2();}return instance;}}class SingletonDemo2Test {public static void main(String[] args) {SingletonDemo2 instance1 = SingletonDemo2.getInstance();SingletonDemo2 instance2 = SingletonDemo2.getInstance();boolean b = instance1 == instance2;System.out.println(b);//输出:true}
}

优点: 延时加载,即在调用方法时产生对象,解决恶汉式资源浪费的问题。
缺点: Synchronized关键字会使代码效率变低
解决方案: DCL懒汉式,即双重检测锁模式,解决代码效率问题。

4.3 DCL懒汉式(双重检测锁模式)

步骤:

  1. 私有化构造器
  2. 类初始化时,不立即加载对象
  3. 提供获取该对象的方法,用volatile关键字避免指令重排带来的问题;缩小Synchronized代码块范围,解决代码效率问题。
//DCL懒汉式(双重检测锁模式)
public class SingletonDemo3 {//私有化构造器private SingletonDemo3() {}//只提供一个实例,并不创建对象//使用避免指令重排带来的线程安全问题//volatile:对于同一个变量,在一个线程中值发生了改变,则在另一个线程中立即生效,可以大幅度避免下面的问题,不排除极端情况private static volatile SingletonDemo3 instance;//提供公共的获取方法,因为不是在类加载时就创建对象,因此存在线程安全问题,使用同步代码块提高效率//现在不需要对整个方法进行同步,缩小了锁的范围,只有第一次会进入创建对象的方法,提高了效率//当第一个线程执行到创建对象的方法时,但还未出方法返回,此时第二个线程进入,发现instance不为空,但第一个线程此时还未出去,可能发送意想不到的安全问题public static SingletonDemo3 getInstance() {if (instance == null) {synchronized (SingletonDemo3.class) {if (instance == null) {instance = new SingletonDemo3();}}}return instance;}
}//测试
class SingletonDemo3Test {public static void main(String[] args) {SingletonDemo3 instance = SingletonDemo3.getInstance();SingletonDemo3 instance1 = SingletonDemo3.getInstance();System.out.println(instance == instance1); //输出true}
}

问题:为什么需要两次判断if(singleTon==null)?

分析

  • 第一次校验:由于单例模式只需要创建一次实例,如果后面再次调用getInstance方法时,则直接返回之前创建的实例,因此大部分时间不需要执行同步方法里面的代码,大大提高了性能。如果不加第一次校验的话,那跟上面的懒汉模式没什么区别,每次都要去竞争锁。

  • 第二次校验:如果没有第二次校验,假设线程t1执行了第一次校验后,判断为null,这时t2也获取了CPU执行权,也执行了第一次校验,判断也为null。接下来t2获得锁,创建实例。这时t1又获得CPU执行权,由于之前已经进行了第一次校验,结果为null(不会再次判断),获得锁后,直接创建实例。结果就会导致创建多个实例。所以需要在同步代码里面进行第二次校验,如果实例为空,则进行创建。

  • 需要注意的是, private static volatile SingletonDemo3 instance;需要加volatile关键字,否则会出现错误问题的原因在于JVM指令重排优化的存在。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。

4.4 静态内部类

步骤:

  1. 私有化构造器
  2. 使用静态内部类(实现延迟加载)
  3. 提供获取该对象的方法

静态内部类:同样也是利用了类的加载机制,它与饿汉模式不同的是,它是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全。

public class SingletonDemo4 {private SingletonDemo4(){};private static  class InnerClass{private static final SingletonDemo4 instance=new SingletonDemo4();}public static SingletonDemo4 getInstance(){return InnerClass.instance;}
}

优点: 线程安全,调用效率高,可延时加载

缺点: 反射机制可以轻易破坏上述单例

class SingletonDemo4Test {public static void main(String[] args) throws Exception {SingletonDemo4 instance1 = SingletonDemo4.getInstance();Constructor<SingletonDemo4> declaredConstructor = SingletonDemo4.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);SingletonDemo4 instance2 = declaredConstructor.newInstance();System.out.println(instance1==instance2);//输出:false}
}

由上面的例子可以得出,反射是可以破坏以上四种的单例模式(这里不一一演示)

那怎样才能解决这个问题呢,我们来看一下反射创建对象的newInstance()方法:


从源码中可以看出,当反射遇到枚举时直接抛出异常,因此,枚举是创建单例的不二之选

4.5 枚举

原理:在反射的源码中,我们发现,反射天然屏蔽枚举类型,所以枚举的对象纯天然是单例的。

public enum  SingletonDemo5 {INSTANCE;public SingletonDemo5 getInstance(){return INSTANCE;}
}
class SingletonDemo5Test {public static void main(String[] args) {SingletonDemo5 instance1 = SingletonDemo5.INSTANCE;SingletonDemo5 instance2 = SingletonDemo5.INSTANCE;boolean b = instance1 == instance2;System.out.println(b);//输出:true}
}

优点: 线程安全,调用效率高,

缺点: 不能延时加载

5.五种实现单例模式的方式的对比

饿汉式:线程安全(不排除反射),调用效率高,不能延时加载
懒汉式:线程安全(不排除反射),调用效率不高,可以延时加载
DCL懒汉式:由于JVM底层模型原因,偶尔出现问题,不建议使用
静态内部类式:线程安全(不排除反射),调用效率高,可以延时加载
枚举单例:线程安全,调用效率高,不能延时加载,避免反射带来的问题。

参考资料:单例模式的五种实现方式

设计模式 | 单例模式相关推荐

  1. Python设计模式-单例模式

    Python设计模式-单例模式 基于Python3.5.2,代码如下 #coding:utf-8 import threading import timeclass Singleton(object) ...

  2. Android设计模式——单例模式(Singleton)

    二十三种设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元 ...

  3. 【学习笔记】ABAP OOD设计模式 - 单例模式

    ABAP OOD设计模式 - 单例模式 整理转自-<SAP ABAP 面向对象程序设计(原则.模式及实践)> 单例模式(Singleton Pattern)是常用的且较为简单的软件设计模式 ...

  4. Go 语言实现 23 种设计模式 单例模式

    Go 语言实现 23 种设计模式 单例模式 单例模式 单例模式是一种常用的软件设计模式,在使用过程中,单例对象的类只有一个实例.使用单例模式,1 可以节省内存等资源,例如windows操作系统的资源管 ...

  5. java singleton inner class_Java面向对象设计模式-单例模式

    Java面向对象设计模式-单例模式 2020-05-28 589 0 单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点,有多重实现方式. 一.饿汉式单例模式,构造方法私有化,在加载类Sin ...

  6. Java开发中常用的设计模式-单例模式

    单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式. Java开发中常用的设计模式-单例模式 单例模式有3个特点: 单例类只有一个实例对象: 该单例对象必须 ...

  7. 炒冷饭系列:设计模式 单例模式

    2019独角兽企业重金招聘Python工程师标准>>> 炒冷饭系列:设计模式 单例模式 摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆 ...

  8. 设计模式-单例模式-注册式单例模式-枚举式单例模式和容器式单例模式在Java中的使用示例

    场景 设计模式-单例模式-饿汉式单例模式.懒汉式单例模式.静态内部类在Java中的使用示例: 设计模式-单例模式-饿汉式单例模式.懒汉式单例模式.静态内部类在Java中的使用示例_霸道流氓气质的博客- ...

  9. 设计模式----创建型设计模式(单例模式、工厂方法模式、构建者模式)

    创建型设计模式 单例模式(Singleton Pattern) 单例模式介绍 代码演示 饿汉式(静态常量) 饿汉式(静态代码块) 懒汉式(线程不安全) 懒汉式(线程安全,同步方法) 懒汉式(线程安全, ...

  10. 小视频源码,设计模式单例模式

    小视频源码,设计模式单例模式实现的相关代码 .一,单线程时候推荐 /*** Created by Shinelon on 2018/10/11.* 单利模式 懒汉式 -->单线程推荐使用*/pu ...

最新文章

  1. Caffe官方教程翻译(2):Web demo
  2. 今天去团省委维护网站
  3. linux mint 18支持安卓编译,Linux mint 18版本开启SSH服务
  4. 计组之中央处理器:5、微程序控制器(组成、原理、概念对比)
  5. openstack 虚拟机如何修改ip地址
  6. Uva - 111 - History Grading
  7. 勒索病毒家族信息判断
  8. Java初级工程师的面试自我介绍模板(仅供参考)
  9. C语言字母大小写转换
  10. Rokid webhook 五步应用指南 手把手教你做个懒人
  11. 帝国cms 未审核 showinfo.php,帝国CMS评论列表标签showplinfo调用会员头像userpic的方法...
  12. 数学中的哈斯图如何构造?附实例
  13. 阿里笔试之Java分词统计
  14. 半桥llc 增益 matlab程序,半桥LLC谐振设计多路输出辅助电源
  15. 宝来客:结婚率创新低,黄金珠宝销售受影响
  16. 2014-04《信息资源管理 02378》真卷解析,逐题解析+背诵技巧
  17. 手写数字识别问题实战
  18. 动手学深度学习——softmax回归之OneHot、softmax与交叉熵
  19. Linux关闭防火墙和端口号问题
  20. exynos4412中断编程

热门文章

  1. endnote使用方法大全,endnote教程
  2. sns.set_style() set_platte()
  3. golden ticket和sliver ticket的区别是什么?
  4. css裁剪图片 clip-path
  5. 使用cecil 完毕 code injection
  6. 类和对象9:属性访问方法
  7. 阿里云服务器默认登录密码是什么?
  8. 未缓存的IPv6路由项链表
  9. centos7批量自动安装
  10. C#自定义控件添加至工具箱的方法