title: 从零玩转设计模式之单例模式
date: 2022-12-12 12:41:03.604
updated: 2022-12-23 15:35:29.0
url: https://www.yby6.com/archives/danlimos
categories:
- 单例模式
- 设计模式
tags:
- Java模式
- 单例模式
- 设计模式

前言

单例设计模式是23种设计模式中最常用的设计模式之一,无论是三方类库还是日常开发几乎都有单例设
计模式的影子。单例设计模式提供了一种在多线程情况下保证实例唯一性的解决方案。单例设计模式虽然简单,但是实现方案却非常多,大体上有以下7种最常见的方式。

饿汉模式

所谓饿汉式,就是不管你用不用这个对象,都先把这个对象进行创建出来,这样子在使用的时候就可以保证是单例。

特点

  • 线程安全性
    在加载的时候已经被实例化,所以只有这一次,线程安全的
  • 懒加载
    没有延迟加载,好长时间不使用,影响性能

示例:

// 没有延迟加载,好长时间不使用,影响性能
public class test1 {/*** 直接初始化对象* */private static final test1 INSTANCE = new test1();/*** 不允许外界进行new对象**/private test1() {}/*** 放行唯一方法 获取对象* @return*/public static test1 getInstance() {return INSTANCE;}
}

总结:
这种方案实现起来最简单,当test1被加载后,就会立即创建instance,因此该方法可以保证百分百的单例,instance不可能被实例化两次。但是这种做instance可能被加载后很长一段时间才会被使用,就意味着instance开辟的内存占用时间更多。

注意:如果一个类中成员属性比较少,且占用内存资源不多,那么就可以使用饿汉式。如果一个类中都是比较重的资源,这种方式就比较不妥

懒汉模式

所谓懒汉式就是在使用时再去创建,可以理解成懒加载。

示例:

public class test2 {private static test2 instance;private test2() {System.out.println("类被实例化了");}public static test2 getInstance() {if (instance == null) {instance = new test2();}return instance;}}

总结:
当instance为null时,getInstance会首先去new一个实例,那之后再将实例返回。

注意:
但是这种实现方式会存在线程安全问题,多个线程同时获取将会出现不同的对象实例,破坏了单例的原则。

懒汉模式+同步方法

为了解决懒汉式线程安全问题,我们可以加上同步方法

特点

  • 直接在方法上进行加锁
  • 锁的力度太大. 性能不是太好
  • synchronized 退化到了串行执行

示例:

public class test2 {private static test2 instance;private test2() {System.out.println("类被实例化了");}public static synchronized test2 getInstance() {if (instance == null) {instance = new test2();}return instance;}}

总结:
这种做法就保证了懒加载又能够百分百保证instance是单例的,但是synchronized关键字天生的排他性导致该方法性能过低。

双重检查锁

Double-Check-Locking是一种比较聪明的做法,我们其实只需要在instance为null时,保证线程的同步性,让只有一个线程去创建对象即可,而其他线程依然是直接使用,而当instance已经有实例之后,我们并不需要线程同步操作,直接并行读即可,这里我们再给类里面加上两个属性.

特点

  • 保证了线程安全
  • 如果实例中存在多个成员属性. 由于在代码执行过程当中,会对代码进行重排,,重排后, 可能导致别一个线程获取对象时初始化属性不正确的情况
  • 加volatile
    • 创建对象步骤

      • memory = allocate(); //1:分配对象的内存空间
        ctorInstance(memory); //2:初始化对象
        instance = memory; //3:设置instance指向刚分配的内存地址
      • memory = allocate(); //1:分配对象的内存空间
        instance = memory; //3:设置instance指向刚分配的内存地址
        //注意,此时对象还没有被初始化!
        ctorInstance(memory); //2:初始化对象
    • 重排问题

示例:

public class test2 {private static test2 instance;private Object o1;private Object o2;private test2() {o1=new Object();o2=new Object();System.out.println("类被实例化了");}public static test2 getInstance() {// 为null时,进入同步代码块,同时避免了每次都需要进入同步代码块if (instance == null) {// 只有一个线程能够获取到锁synchronized (test2.class) {// 如果为Null在创建if (instance == null) {instance = new test2();}}}return instance;}}

总结:
当两个线程发现 instance == null 时,只有一个线程有资格进入同步代码块,完成对instance的初始化,随后的线程再次进入同步代码块之后,因为 instance == null 不成立,就不会再次创建,这是未加载情况下并行的场景,而instance加载完成后,再有线程进入getInstance方法后,就直接返回
instance,不会进入到同步代码块,从而提高性能。

注意:
这种做法看似完美和巧妙,既满足懒加载,又保证instance的唯一性,但是这种方式实际上是会出现空指针异常的。

解析空指针异常的问题:

在test2构造方法中,我们会初始化 o1 和 o2两个资源,还有Single自身,而这三者实际上并无前后关系的约束,那么极有可能JVM会对其进行重排序,导致先实例化test2,再实例化o1和o2,这样在使用test2时,可能会因为o1和o2没有实例化完毕,导致空指针异常。

双重检查锁+volatile

解决上面的方法其实很简单,给instance加上一个volatile关键字即可,这样就防止了重排序导致的程序异常。

private volatile static test2 instance;

内部类(Holder)方式

holder方式借助了类加载的特点,我们直接看代码。

public class test3 {private test3() {System.out.println("类被实例化了");}/*** 使用内部类方式不会主动加载,只有主类被使用的时候才会进行加载* 第一次使用到的时候才去执行  只执行一次*/private static class Holder {private static test3 instance = new test3();}/*** 提供外界进行调用* @return*/public static test3 getInstance() {return Holder.instance;}}

特点

  • 它结合了饿汉模式 安全性,也结合了懒汉模式懒加载。不会使用synchronized 所以性能也有所保证

  • 声明类的时候,成员变量中不声明实例变量,而放到内部静态类中

  • 不存在线程安全问题

  • 懒加载的

    • 反序列化问题
      // 该方法在反序列化时会被调用
      protected Object readResolve() throws ObjectStreamException {
      System.out.println("调用了readResolve方法!");
      return Hoder.instance;
      }

总结:
我们发现,在test3中并没有instance,而是将其放到了静态内部类中,使用饿汉式进行加载。但是实际上这并不是饿汉式。因为静态内部类不会主动加载,只有主类被使用时才会加载,这也就保证了程序运行时并不会直接创建一个instance而浪费内存,当我们主动引用Holder时,才会创建instance实例,从而保证了懒加载。

枚举方式

枚举的方式实现单例模式是《Effective Java》作者力推的方式,枚举类型不允许被继承,同样是线程安全的并且只能被初始化一次。但是使用枚举类型不能懒加载,比如下面的代码,一旦使用到里面的静态方法,INSTANCE就会立即被实例化。

特点

  • 不存在线程安全
  • 没有懒加载

示例:

public enum test4 {INSTANCE;test4() {System.out.println("类被实例化了");}public static test4 getInstance() {return INSTANCE;}
}

源码

  • Runtime类
  • Mybatis ErrorContext
  • 类加载器

从零玩转设计模式之单例模式-danlimos相关推荐

  1. 百度工程师教你玩转设计模式(观察者模式)

    要写好代码,设计模式(Design Pattern)是必不可少的基本功,设计模式是对面向对象设计(Object Oriented Design)中反复出现的问题的一种有效解决方案,本次从比较常见的观察 ...

  2. socket可以写成单例嘛_精读《设计模式 - Singleton 单例模式》

    Singleton(单例模式) Singleton(单例模式)属于创建型模式,提供一种对象获取方式,保证在一定范围内是唯一的. 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 其实单例模 ...

  3. socket可以写成单例嘛_精读设计模式 Singleton 单例模式

    Singleton(单例模式) Singleton(单例模式)属于创建型模式,提供一种对象获取方式,保证在一定范围内是唯一的. 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 其实单例模 ...

  4. GoLang设计模式之单例模式

    文章目录 GoLang设计模式之单例模式 1.单例模式概念 2.单例模式优点 3.单例模式应用实例 4.单例模式使用场景 5.单例模式实现方式 6.懒汉模式 6.1概念 6.2不加锁实现 6.3整个方 ...

  5. C#设计模式(1)——单例模式

    原文地址:http://www.cnblogs.com/zhili/p/SingletonPatterm.html 一.引言 最近在设计模式的一些内容,主要的参考书籍是<Head First 设 ...

  6. java设计模式之单例模式(七种方法)

    单例模式:个人认为这个是最简单的一种设计模式,而且也是在我们开发中最常用的一个设计模式. 单例模式的意思就是只有一个实例.单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个 ...

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

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

  8. go设计模式之单例模式

    在这里插入图片描述# go设计模式之单例模式 在软件开发时,经常会遇到有些对象需要保证只有一个实例的,那么这种设计模式就应用而生. 定义 单例模式,也叫单子模式,是一种常用的软件设计模式,属于创建型模 ...

  9. 一篇博客读懂设计模式之---单例模式

    一篇博客读懂设计模式之---单例模式 一.  单例模式 单例对象(Singleton)是一种常用的设计模式.在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在.这样的模式有几个好处 ...

最新文章

  1. Excel随机选取指定数据
  2. ORACLE姚翔,- oalib
  3. H5新增的标签以及属性
  4. 解读电感和电容在交流电路中的作用
  5. cmail服务器安装后无法登录的解决办法
  6. Java--对象内存布局
  7. 浮点数在计算机中起什么作用,浮点数在计算机中的存储表示
  8. 程序员求生指南:告别大小周,摆脱监视,直奔年终奖!
  9. 6 年成为 AIoT 独角兽,这位 17 年连续创业者是如何做到的?
  10. Ubuntu中如何打开终端terminal
  11. Digester 解析遇到字符失败
  12. 远程桌面连接阿里云服务器
  13. visio和office安装冲突
  14. 【UE·蓝图底层篇】一文搞懂NativeClass、GeneratedClass、BlueprintClass、ParentClass
  15. 基于Android的sina微博分享功能
  16. TreeView详解
  17. 如何将网站上传到服务器空间,如何使用FTP工具将网站上传到虚拟主机空间
  18. nginx禁止外网访问
  19. iOS15只是一个更好看的Linux吗?
  20. JavaScript函数的使用以及下拉框、文本框、radio值的获取,结合一个淘宝竞价案例。。。

热门文章

  1. Idea使用Tomcat详细步骤
  2. 谷歌浏览器安装QQ旋风插件
  3. GBase 8c V3.0.0数据类型——HLL函数和操作符(操作符)
  4. 简约手绘竞聘通用PPT模板
  5. 市场上的四种呼叫中心系统方案
  6. 【转】深入解析Jolt
  7. linux之hdparm命令说明及其测试硬盘读写速度
  8. BB FlashBack单独导出音频
  9. 电脑投屏到手机/平板上做第二屏幕(显示器)
  10. 数据库 查询计算机系姓王,数据库上机实验报告——SQL Server 2008 简单查询.doc