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. 《从零玩转JavaWeb+项目实战》-系列课堂录制计划

    点击试听课程 前言 很多自学编程的同学经常和我说想学一门语言自己到网上找一些教程看到一半就像背单词背到ambulance一样坚持不下去了....究其原因基本上都是:内容太多,太枯燥,专业术语听不懂,学 ...

  2. 设计模式之单例模式(Singleton)

    设计模式之单例模式(Singleton) 设计模式是前辈的一些经验总结之后的精髓,学习设计模式可以针对不同的问题给出更加优雅的解答 单例模式可分为俩种:懒汉模式和饿汉模式.俩种模式分别有不同的优势和缺 ...

  3. GJM : C#设计模式(1)——单例模式

    感谢您的阅读.喜欢的.有用的就请大哥大嫂们高抬贵手"推荐一下"吧!你的精神支持是博主强大的写作动力以及转载收藏动力.欢迎转载! 版权声明:本文原创发表于 [请点击连接前往] ,未经 ...

  4. java设计模式之单例模式(几种写法及比较)

    概念: Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建 ...

  5. 每天一个设计模式-4 单例模式(Singleton)

    每天一个设计模式-4 单例模式(Singleton) 1.实际生活的例子 有一天,你的自行车的某个螺丝钉松了,修车铺离你家比较远,而附近的五金店有卖扳手:因此,你决定去五金店买一个扳手,自己把螺丝钉固 ...

  6. 设计模式之单例模式的简单demo

    /* * 设计模式之单例模式的简单demo */ class Single { /* * 创建一个本类对象. * 和get/set方法思想一样,类不能直接调用对象 * 所以用private限制权限 * ...

  7. 设计模式之单例模式——Singleton

                        设计模式之单例模式--Singleton 设计意图: 保证类仅有一个实例,并且可以供应用程序全局使用.为了保证这一点,就需要这个类自己创建自己的对象,并且对外有 ...

  8. 10月27日PHP加载类、设计模式(单例模式和工厂模式)、面向对象的六大原则

    加载类可以使用include.require.require_once三种中的任意一种,每个关键字都有两种方法,但是这种方法的缺点是需要加载多少个php文件,就要写多少个加载类的方法.一般也就需要加载 ...

  9. java 23 - 2 设计模式之单例模式

    单例模式:保证类在内存中只有一个对象. 如何保证类在内存中只有一个对象呢?  A:把构造方法私有  B:在成员位置自己创建一个对象  C:通过一个公共的方法提供访问 单例模式之饿汉式: (一进来就造对 ...

  10. [转]JAVA设计模式之单例模式

    原文地址:http://blog.csdn.net/jason0539/article/details/23297037 概念: java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主 ...

随机推荐

  1. Solution -「洛谷 P5072」「YunoOI 2015」盼君勿忘

    Description Link. 无修支持查询:查询一个区间 \([l,r]\) 中所有子序列分别去重后的和 \(\bmod\ p\) Solution 这是数据结构一百题的第50题(一半了哦)的纪 ...

  2. mpi转以太网连接300PLC与施耐德 Quantum PLC 通讯

    S7300 PLC转以太网无需编程与施耐德 Quantum PLC modbusTCP通信 方案介绍: 西门子300PLC转以太网不需要编写程序通过兴达易控MPI-ETH-XD1.0与施耐德 Quan ...

  3. 6.swagger完善:界面显示注释+多版本控制

    周末,写点简单的水一下. 新版本的vs创建项目的时候可以选择自带一个swagger.然而这只是基本的swagger功能. 几个接口无所谓啦,随着接口越来越多,就这么丢给你,一时间也会懵逼,所以这篇文章 ...

  4. 2023-10-04:用go语言,现有一棵无向、无根的树,树中有 n 个节点,按从 0 到 n - 1 编号 给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges , 其中 edge

    2023-10-04:用go语言,现有一棵无向.无根的树,树中有 n 个节点,按从 0 到 n - 1 编号 给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges , 其中 edge ...

  5. VTable——不只是高性能的多维数据分析表格

    导读 VTable: 不只是高性能的多维数据分析表格,更是行列间创作的方格艺术家! VTable是字节跳动开源可视化解决方案 VisActor 的组件之一. 在现代应用程序中,表格组件是不可或缺的一部 ...

  6. 轻巧的批量图片压缩工具imgfast

    现在的手机拍照动辄2M3M,还有7M8m的,如果要把这些文件上传到网上应用,浪费网络,占用资源 所以2022年中秋写了这个小工具,可以批量进行图片文件压缩,支持jpg和png. 文件下载链接https ...

  7. 如何在云服务上快速拥有洛甲WAF(Web防火墙)

    如何在云服务上快速拥有洛甲WAF(Web防火墙) 洛甲WAF是基于openresty的web防火墙,通过配合后台保护您的数据安全,详情参考节点服务器 luojiawaf_lua(nginx+lua) ...

  8. 自定义MyBatis拦截器更改表名

    by emanjusaka from ​ https://www.emanjusaka.top/archives/10 彼岸花开可奈何 本文欢迎分享与聚合,全文转载请留下原文地址. 自定义MyBati ...

  9. calico网络异常,不健康

    解决calico/node is not ready: BIRD is not ready: BGP not established withxxx calico有一个没有ready,查了一下是没有发 ...

  10. NOI 2023 游记

    Day0=2023.7.23. Day -?? 订了 30 个徽章.很快认识到可能不够,又自己买了 30 个. Day -? UNR,完全没有精神状态于是两天都考半场睡半场.0+10+55,成功 Fe ...