单例模式,饿汉与懒汉

文章目录

  • 什么是单例模式
  • 单例模式的两种形式
    • 饿汉模式
    • 懒汉模式
  • 懒汉模式与饿汉模式是否线程安全
  • 懒汉模式的优化

什么是单例模式

单例模式其实就是一种设计模式,跟象棋的棋谱一样,给出一些固定的套路帮助你更好的完成代码。设计模式有很多种,单例模式是在校招当中最爱考的设计模式之一。

单例就指的是单个实例,一个程序如果频繁使用一个对象且作用相同,为了防止多次实例化对象,我们就可以使用单例模式,让类只能创建出一个实例,也就是一个对象,减少开销。
有一些场景本身就是要求某一个概念是单例的,例如JDBC里的DateSores

单例模式的两种形式

在Java中实现单例模式有很多种写法,我们这里重点讲解两种,懒汉模式与饿汉模式。

饿汉模式

饿汉模式,顾名思义,当人非常饿的时候,看见了食物,那种心情是怎么样的迫不及待。饿汉模式非常着急在类进行创建时就已经迫不及待的实例化单例对象了

class Singleton {
    private static Singleton singleton = new Singleton();

    public static Singleton getInstance() {
        return singleton;
    }
}


根据我们的描述可以写出这样的代码,但是我们发现,单例模式的初心我们并没有达到,单例模式的初心是让类只能实例化一次,此时我们并没有完成需求。我们通过私有化构造方法的方式来防止类的多次实例化:

class Singleton {
    private static Singleton singleton = new Singleton();

    public static Singleton getInstance() {
        return singleton;
    }

    private Singleton () {}
}

此时我们单例模式中的饿汉模式就已经完成了,我们可以来测试一下:
在这里插入图片描述

懒汉模式

懒汉,所表示的含义并不是我们理解的流浪汉,相反懒表示的是一种从容不迫,是不着急,这种模式与饿汉模式的迫不及待不同,他只有在真正需要使用对象时才实例化单例对象。懒汉模式同样使用私有化构造方法的形式来完成初心,我们来写一下代码:

class SingletonLazy {
  private static SingletonLazy singletonLazy = null;

  public static SingletonLazy getInstance() {
      if(singletonLazy == null) {
          singletonLazy = new SingletonLazy();
      }
      return singletonLazy;
  }

  private SingletonLazy () {}
}

懒汉模式与饿汉模式是否线程安全

上面我们完成了懒汉模式与饿汉模式的代码编写,现在我们需要考虑一个问题,上面两个代码,是否线程安全,在多线程下调用getInstance()是否会出现问题。

首先我们来看饿汉模式
在这里插入图片描述
饿汉模式的getInstance()方法为只读操作,所以在多线程下调用不会有什么问题,是安全的。

懒汉模式:
在这里插入图片描述
懒汉模式在多线程下,无法保证创建对象的唯一性。

例如两个线程同时调用getInstance()方法,代码的执行顺序可能为:
1、线程一进行判断操作
2、线程二进行判断操作
3、线程一实例化对象
4、线程二实例化对象
这样线程一和线程二都会实例化对象,如果是N个线程可能会实例化N个对象,所以懒汉模式在多线程模式下不安全。

懒汉模式的优化

我们需要对懒汉模式进行优化,使得他在多线程下变得安全,如何操作呢?上面的分析中我们提到了,懒汉模式不安全的原因是,判断操作和new操作没有原子性,那么我们让他具有原子性不就可以了。我们就可以通过加锁来完成需求:

class SingletonLazy {
  private static SingletonLazy singletonLazy = null;

  public static SingletonLazy getInstance() {
      synchronized (SingletonLazy.class) {
          if(singletonLazy == null) {
              singletonLazy = new SingletonLazy();
          }
      }
      return singletonLazy;
  }

  private SingletonLazy () {}
}

这样就会有锁竞争,不会在出现向刚才那样两个线程同时进行判断的操作,一定是等一个线程new了之后,另一个线程才能竞争到锁进行判断。
我们觉得这样还是不够,不够高效,这样写虽然可以解决安全问题,但是同时也造成了效率的降低,每个线程都需要阻塞等待,但是我们分析一下,只有singletonLazy == null时才需要进行阻塞,当singletonLazy != null时其实就只是单纯的读操作。所以我们在进行优化:

class SingletonLazy {
    private static SingletonLazy singletonLazy = null;

    public static SingletonLazy getInstance() {
        if (singletonLazy == null) {
            synchronized (SingletonLazy.class) {
                if(singletonLazy == null) {
                    singletonLazy = new SingletonLazy();
                }
            }
        }
        return singletonLazy;
    }

    private SingletonLazy () {}
}

这样又解决了我们的问题,上面代码中的两个判断条件看着是一样的,但是初心不一样,第一个是为了提高效率,判断是否需要加锁,第二个是为了判断是否需要实例化对象,两行代码看着离这不远,但是中间有一个加锁的操作,执行的时机其实差别很大。
这样就完了?并没有这里还有一个问题:指令重排序

什么是指令重排序呢?
创建一个对象,在jvm中会经过三步
1、创建内存空间
2、调用构造方法
3、将引用指向分配好的内存空间
我们发现,第二步和第三步好像可以进行交换执行顺序,交换之后对结果并没有影响,而这样不影响结果的情况下,可以不按照程序编码的顺序执行语句,提高程序性能的操作,我们称为指令重排序

这里我们也实力化对象了,所以也可能有指令重排序的操作,例如线程一此时new对象的时候,发生了指令重排序,在没有调用构造方法的情况下进行了分配内存空间,此时系统调度到了线程二,线程二进行判断,此时引用非空就返回,这样我们返回了一个没有调用过构造方法的引用。
如何解决问题呢?我们使用volatile就可以防止指令重排序:

class SingletonLazy {
    volatile private static SingletonLazy singletonLazy = null;

    public static SingletonLazy getInstance() {
        if (singletonLazy == null) {
            synchronized (SingletonLazy.class) {
                if(singletonLazy == null) {
                    singletonLazy = new SingletonLazy();
                }
            }
        }
        return singletonLazy;
    }

    private SingletonLazy () {}
}

这样懒汉模式的优化,我们就完成了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/2373.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu-C语言下的应用

文章目录一、Ubuntu下C语言的应用(一)如何使用gedit创建/打开/保存/关闭文件(二)gedit中相关参数配置:首选项(三)ubuntu下C语言的编译器 -- gcc一、Ubuntu下C语言的应用 (一&#x…

GPIO四种输入和四种输出模式

GPIO的结构图如下所示: 最右端为I/O引脚,左端的器件位于芯片内部。I/O引脚并联了两个用于保护的二极管。 输入模式 从I/O引脚进来就遇到了两个开关和电阻,与VDD相连的为上拉电阻,与VSS相连的为下拉电阻。再连接到TTL施密特触发…

机器学习算法——决策树详解

文章目录前言:决策树的定义熵和信息熵的相关概念信息熵的简单理解经典的决策树算法ID3算法划分选择或划分标准——信息增益ID3算法的优缺点C4.5算法信息增益率划分选择或划分标准——Gini系数(CART算法)Gini系数计算举例CART算法的优缺点其他…

RK3588平台开发系列讲解(显示篇)DP显示调试方法

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、查看 connector 状态二、强制使能/禁⽤ DP三、DPCP 读写四、Type-C 接口 Debug五、查看 DP 寄存器六、查看 VOP 状态七、查看当前显示时钟八、调整 DRM log 等级沉淀、分享、成长,让自己和他人都能有所收获!😄…

C++成神之路 | 第一课【步入C++的世界】

目录 一、认识C++ 1.1、关于 C++ 1.2、C++的前世今生 1.2.1、C+

【分享NVIDIA GTC大会干货】与Jetson嵌入式平台工程师的深度挖掘问答

Connect with the Experts: A Deep-Dive Q&A with Jetson Embedded Platform Engineers [CWES52132]NVIDIA Jetson 是世界领先的边缘人工智能计算平台。它具有高性能和低功耗的特点,是机器人、无人机、移动医疗成像和智能视频分析等计算密集型嵌入式应用的理想选…

【蓝桥杯集训·每日一题】AcWing 1051. 最大的和

文章目录一、题目1、原题链接2、题目描述二、解题报告1、思路分析2、时间复杂度3、代码详解三、知识风暴线性DP一、题目 1、原题链接 1051. 最大的和 2、题目描述 对于给定的整数序列 A{a1,a2,…,an},找出两个不重合连续子段,使得两子段中所有数字的和最…

半入耳式蓝牙耳机哪款音质好?音质最好的半入耳蓝牙耳机推荐

蓝牙耳机分为头戴式、入耳式和半入耳式三种,大多数人都会选择入耳式和半入耳式,因为它的体积小,重量轻,更适合于随身携带。半入耳式采用了平头塞设计,大部分都位于耳道之外,所以戴起来更加舒适,…

【kubernetes云原生】k8s标签选择器使用详解

目录 一、标签选择器来源 二、什么是标签选择器 2.1 标签选择器概述 2.2 标签选择器概述属性 三、标签使用场景 四、标签选择器特点 4.1 基本特点 4.2 核心标签选择器 4.3 补充说明 五、标签选择器常用操作命令 5.1 前置准备 5.2 常用操作命令 5.2.1 查看namespac…

使用Android架构模板

使用Android架构模板 项目介绍 为了方便开发者引入最新的Android架构组建进行开发,Google官方给我们引入了一个架构模板,方便我们快速进入开发。 github地址: https://github.com/android/architecture-templates 该模板遵循官方架构指南 …

小白怎么系统的自学计算机科学和黑客技术?

我把csdn上有关自学网络安全、零基础入门网络安全的回答大致都浏览了一遍,最大的感受就是“太复杂”,新手看了之后只会更迷茫,还是不知道如何去做,所以站在新手的角度去写回答,应该把回答写的简单易懂,“傻…

Maven依赖加载不进来? 依赖加载失败? 你值得掌握如何排查的方法

本文目录前言1. 确认是否添加了spring-boot-starter-web依赖2. 如果添加了spring-boot-starter-web依赖,刷新以后还飘红?3. 确认父项目加spring-boot-dependencies了吗?3.1 如果是因为没加spring-boot-dependencies3.2 如果已经加了spring-bo…

22张图带你了解IP地址有什么作用

了解IP地址 1、IP地址的格式 在IP协议的报文中,可以得知IP地址是有32个比特,IP地址在计算机中是以二进制的方式处理的,如果全部以二进制的形式来表示,使用跟表达都非常的困难,所以为了人类方便记忆,采用了…

TCP/UDP协议

写在前面 下面我们继续说我们传输层的协议,这个协议我们重点看TCP协议,注意TCP是我们经常使用的额,而且也是我们在面试最容易被问到的,所以我们要重点分析,下面是一张图,让我们回忆一下我们知识点到协议的哪一层了. 再谈端口号 我们知道端口号(Port)标识了一个主机上进行通信的…

字符串的反转以及巧用反转 ------关于反转,看这一篇就足够了

目录 一.本文介绍 二.反转字符串 1.题目描述 2.问题分析 3.代码实现 三.反转字符串 II 1.题目描述 2.问题分析 3.代码实现 三.反转字符串中的单词 I 1.题目描述 2.问题分析 3.代码实现 四.反转字符串中的单词 III 1.题目描述 2.问题分析 3.代码实现 五.仅仅反…

【Python入门第三十五天】Python丨文件打开

在服务器上打开文件 假设我们有以下文件,位于与 Python 相同的文件夹中。 demofile.txt Hello! Welcome to demofile.txt This file is for testing purposes. Good Luck!如需打开文件,请使用内建的 open() 函数。 open() 函数返回文件对象&#xff…

Linux驱动开发——串口设备驱动

Linux驱动开发——串口设备驱动 一、串口简介 串口全称叫做串行接口,通常也叫做 COM 接口,串行接口指的是数据一个一个的顺序传输,通信线路简单。使用两条线即可实现双向通信,一条用于发送,一条用于接收。串口通信距…

基于uniapp+u-view开发小程序【技术点整理】

一、上传图片 1.实现效果&#xff1a; 2.具体代码&#xff1a; <template><view><view class"imgbox"><view>职业证书</view><!-- 上传图片 --><u-upload :fileList"fileList1" afterRead"afterRead"…

C语言蓝桥杯刷题:修剪灌木

题目链接 解题思路&#xff1a; 本题需要注意的是树是白天长&#xff0c;然后爱丽丝傍晩对某棵树进行修剪&#xff0c;也就是说树高度是可能在白天达到最大值而在傍晩变成0。我一开始也有一个误区&#xff0c;以为是要修剪的那棵树当天就变成0而不能生长&#xff0c;其实是先…

vue后台管理系统

后面可参考下&#xff1a;vue系列&#xff08;三&#xff09;——手把手教你搭建一个vue3管理后台基础模板 以下代码项目gitee地址 文章目录1. 初始化前端项目初始化项目添加加载效果配置 vite.config.js2. 使用路由安装路由配置路由配置别名和跳转安装pathvite.config.jsjsco…