当前位置: 首页 > news >正文

单例模式之「双重校验锁」

单例模式之「双重校验锁」

单例模式

  • 单例即单实例,只实例出来一个对象。
  • 一般在创建一些管理器类、工具类的时候,需要用到单例模式,比如JDBCUtil 类,我们只需要一个实例即可(多个实例也可以实现功能,但是增加了代码量且降低了性能)。

如何实现单例

  • 将构造方法私有化
  • 提供一个全局唯一获取该类实例的方法帮助用户获取类的实例

应用场景

  • 主要被用于一个全局类的对象在多个地方被使用并且对象的状态是全局变化的场景下。

单例模式的优点

  • 单例模式为系统资源的优化提供了很好的思路,频繁创建和销毁对象都会增加系统的资源消耗,而单例模式保障了整个系统只有一个对象能被使用,很好地节约了资源。

单例模式的写法

  • 饿汉模式
  • 懒汉模式
  • 静态内部类
  • 双重校验锁

饿汉模式

  • 顾名思义,饿汉模式就是加载类的时候直接new一个对象,后面直接用即可。

  • 饿汉模式指在类中直接定义全局的静态对象的实例并初始化,然后提供一个方法获取该实例对象。

  • 代码如下:

  • public class Singleton {// 使用static修饰,类加载的时候new一个对象private static Singleton INSTANCE = new Singleton();// 构造器私有化private Singleton() {}public static Singleton getInstance() {return INSTANCE;}
    }
    

懒汉模式

  • 顾名思义,懒汉模式就是加载类的时候只声明变量,不new对象,后面用到的时候再new对象,然后把对象赋给该变量。

  • 定义一个私有的静态对象INSTANCE,之所以定义INSTANCE为静态,是因为静态属性或方法是属于类的,能够很好地保障单例对象的唯一性;

  • 然后定义一个静态方法获取该对象,如果对象为null,则 new 一个对象并将其赋值给INSTANCE。

  • 代码如下:

  • public class Singleton {private static Singleton INSTANCE;// 构造器私有化private Singleton() {}public static Singleton getInstance() {if (INSTANCE == null) {INSTANCE = new Singleton();}return INSTANCE;}
    }
    
饿汉模式和懒汉模式的区别在于
  • 饿汉模式是在类加载时将其实例化的,在饿汉模式下,在Class Loader完成后该类的实例便已经存在于JVM中了,即,在getInstance方法第一次被调用前该实例已经存在了,new对象的操作不在getInstance方法内。
  • 而懒汉模式在类中只是定义了变量但是并未实例化,实例化的过程是在获取单例对象的方法中实现的,即,在getInstance方法第一次被调用后该实例才会被创建,new对象的操作在getInstance方法内。
  • 此外注意:饿汉模式的实例在类加载的时候已经存在于JVM中了,因此是线程安全的;懒汉模式通过第一次调用getInstance才实例化,该方法不是线程安全的(后面讲怎么优化)

静态内部类

  • 静态内部类通过在类中定义一个静态内部类,将对象实例的定义和初始化放在内部类中完成,我们在获取对象时要通过静态内部类调用其单例对象。

  • 之所以这样设计,是因为类的静态内部类在JVM中是唯一的,这很好地保障了单例对象的唯一性。
    静态内部类的单例实现方式同样是线程安全的。

  • 代码如下:

  • public class Singleton {private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}private Singleton(){}public static final Singleton getInstance(){return SingletonHolder.INSTANCE;}
    }
    
  • 饿汉模式和静态内部类实现单例模式的优点是写法简单,缺点是不适合复杂对象的创建。对于涉及复杂对象创建的单例模式,比较优雅的实现方式是懒汉模式,但是懒汉模式是非线程安全的,下面就讲一下懒汉模式的升级版——双重构校验锁模式(双重构校验锁是线程安全的)。

双重构校验锁

  • 饿汉模式是不需要加锁来保证单例的,而懒汉模式虽然节省了内存,但是却需要使用锁来保证单例,因此,双重校验锁就是懒汉模式的升级版本。

单线程懒汉模式实现

  • 普通的懒汉模式在单线程场景下是线程安全的,但在多线程场景下是非线程安全的。

  • 先来看看普通的懒汉模式实现:

  • public class Singleton {private static Singleton INSTANCE;private Singleton() {}public static Singleton getInstance() {if (INSTANCE == null) {INSTANCE = new Singleton();}return INSTANCE;}
    }
    

单线程懒汉模式的问题

  • 上面这段代码在单线程环境下没有问题,但是在多线程的情况下会产生线程安全问题。

  • 在多个线程同时调用getInstance方法时,由于方法没有加锁,可能会出现以下情况

  • ① 这些线程可能会创建多个对象

  • ② 某个线程可能会得到一个未完全初始化的对象

  • 为什么会出现以上问题?

  • 对于 ① 的情况解释如下:

  • public static Singleton getInstance() {if (INSTANCE == null) {/*** 由于没有加锁,当线程A刚执行完if判断INSTANCE为null后还没来得及执行INSTANCE = new Singleton()* 此时线程B进来,if判断后INSTANCE为null,且执行完INSTANCE = new Singleton()* 然后,线程A接着执行,由于之前if判断INSTANCE为null,于是执行INSTANCE = new Singleton()重复创建了对象*/INSTANCE = new Singleton();}return INSTANCE;
    }
    
  • 对于 ② 的情况解释如下:

  • public static Singleton getInstance() {if (INSTANCE == null) {/*** 由于没有加锁,当线程A刚执行完if判断INSTANCE为null后开始执行 INSTANCE = new Singleton()* 但是注意,new Singleton()这个操作在JVM层面不是一个原子操作**(具体由三步组成:1.为INSTANCE分配内存空间;2.初始化INSTANCE;3.将INSTANCE指向分配的内存空间,* 且这三步在JVM层面有可能发生指令重排,导致实际执行顺序可能为1-3-2)** 因为new操作不是原子化操作,因此,可能会出现线程A执行new Singleton()时发生指令重排的情况,* 导致实际执行顺序变为1-3-2,当执行完1-3还没来及执行2时(虽然还没执行2,但是对象的引用已经有了,* 只不过引用的是一个还没初始化的对象),此时线程B进来进行if判断后INSTANCE不为null,* 然后直接把线程A new到一半的对象返回了*/INSTANCE = new Singleton();}return INSTANCE;
    }
    

解决问题:加锁

  • 为了解决问题 ①,我们可以对 getInstance() 这个方法加锁。

  • public class Singleton {private static Singleton INSTANCE;private Singleton() {}public static synchronized Singleton getInstance() {  // 加锁if (INSTANCE == null) {INSTANCE = new Singleton();}return INSTANCE;}
    }
    
  • 仔细看,这里是粗暴地对整个 getInstance() 方法加锁,这样做代价很大,因为,只有当第一次调用 getInstance() 时才需要同步创建对象,创建之后再次调用 getInstance() 时就只是简单的返回成员变量,而这里是无需同步的,所以没必要对整个方法加锁。

  • 由于同步一个方法会降低上百倍甚至更高的性能, 每次调用获取和释放锁的开销似乎是可以避免的:一旦初始化完成,获取和释放锁就显得很不必要。

  • 所以可以只对方法的部分代码加锁!

  • public class Lock2Singleton {private static Lock2Singleton INSTANCE;private Lock2Singleton() {}public static Lock2Singleton getSingleton() {// 因为INSTANCE是静态变量,所以给Lock2Singleton的Claa对象上锁synchronized(Lock2Singleton.class) {        // 加 synchronizedif (INSTANCE == null) {INSTANCE = new Lock2Singleton();}}return INSTANCE;}
    }
    
  • 优化后的代码选择了对 if (INSTANCE == null) 和 INSTANCE = new Lock2Singleton()加锁

  • 这样,每个线程进到这个方法中之后先加锁,这样就保证了 if (INSTANCE == null) 和 INSTANCE = new Lock2Singleton() 这两行代码被同一个线程执行时不会有另外一个线程进来,由此保证了创建的对象是唯一的。

  • 对象的唯一性保证了,也就是解决了问题①,但是如何解决问题②呢?虽然加了 synchronized,但是 synchronized 是不能禁止指令重排的,也就是说,INSTANCE = new Lock2Singleton(); 这行代码在 JVM 层面还是有可能发生 1-3-2 的现象,那要怎么保证绝对的1-2-3顺序呢,也就是禁止指令重排序,答案是加 volatile 关键字。

  • public class Lock2Singleton {private volatile static Lock2Singleton INSTANCE; // 加 volatileprivate Lock2Singleton() {}public static Lock2Singleton getSingleton() {synchronized(Lock2Singleton.class) {         // 加 synchronizedif (INSTANCE == null) {INSTANCE = new Lock2Singleton();}}return INSTANCE;}
    }
    
  • 这样总可以解决问题 ① 和 ② 了吧,然而你以为这就结束了吗?NO!这段代码从功能层面来讲确实是已经结束了,但是性能方面呢?是不是还有可以优化的地方?

  • 答案是:有!!

  • 值得优化的地方就在于 synchronized 代码块这里。每个线程进来,不管三七二十一,都要先进入同步代码块再说,如果说现在 INSTANCE 已经不为null了,那么,此时当一个线程进来,先获得锁,然后才会执行 if 判断。我们知道加锁是非常影响效率的,所以,如果 INSTANCE 已经不为null,是不是就可以先判断,再进入 synchronized 代码块。如下

  • public class Lock2Singleton {private volatile static Lock2Singleton INSTANCE;    // 加 volatileprivate Lock2Singleton() {}public static Lock2Singleton getSingleton() {if (INSTANCE == null) {                         // 双重校验:第一次校验synchronized(Lock2Singleton.class) {        // 加 synchronizedif (INSTANCE == null) {                 // 双重校验:第二次校验INSTANCE = new Lock2Singleton();}}}return INSTANCE;}
    }
    
  • 在 synchronized 代码块之外再加一个 if 判断,这样,当 INSTANCE 已经存在时,线程先判断不为null,然后直接返回,避免了进入 synchronized 同步代码块。

  • 那么可能又有人问,好了,我明白了在 synchronized 代码块外加一个 if 判断,是不是就意味着里面的那个 if 判断可以去掉?

  • 当然不可以!!

  • 如果把里面的 if 判断去掉,就相当于只对 INSTANCE = new Lock2Singleton() 这一行代码加了个锁,只对一行代码加锁,那你岂不是加了个寂寞(加锁的目的就是防止在第二个if判断和new操作之间有别的线程进来!!),结果还是会引起问题①。

  • 所以,两次校验,一次都不能少!!

总结

  • 最终,单例模式双重校验锁模式的完整代码实现如下:

  • public class Lock2Singleton {private volatile static Lock2Singleton INSTANCE;    // 加 volatileprivate Lock2Singleton() {}public static Lock2Singleton getSingleton() {if (INSTANCE == null) {                         // 双重校验:第一次校验synchronized(Lock2Singleton.class) {        // 加 synchronizedif (INSTANCE == null) {                 // 双重校验:第二次校验INSTANCE = new Lock2Singleton();}}}return INSTANCE;}
    }
    
  • 过程如下:

  • 判断 INSTANCE 是否为null,检查变量是否被初始化(不去获得锁),如果已被初始化立即返回这个变量;

  • 不为null,直接返回,不用去竞争锁

  • 为null,获取锁,然后再次判断(虽然已经判断过,但是在第一个if和synchronized之间仍有可能被另外线程插入导致第一个if判断为null时,当进入同步代码块之后再次判断时已经不为null了,所以需要再次判断)

  • 是否为null

  • 为null,创建并返回

  • 不为null,直接直接返回

  • 为什么是双重校验 ?

  • 第二次校验是为了解决问题①,即避免多个线程重复创建对象。

  • 第一次校验是为了提高效率,避免 INSTANCE 不为null时仍然去竞争锁。

  • 为什么加 volatile ?

  • 加 volatile 是为了禁止指令重排序,也就是为了解决问题②,即避免某个线程获取到其他线程没有初始化完全的对象。

-----------------------------------------------------------------------------------

offer突击训练营简介:

1:针对不知道怎么面试,面试没有信心的小伙伴,我们会给你一个offer保障。

2:我们会监督你15-20天内把面试体系技术点掌握至少7成,这样足够你去找到满意的工作了。

3:我们是面向面试学习指导,不会带你们去写代码,会把项目真实开发的迭代过程和技术细节如何实现业务功能都详细教清楚,你能在面试中流畅表达清楚就行了,项目经验你不用担心(技术老师提供的真实项目经验肯定拿的出手),自己学和别人带着系统学,效率完全不一样。

详情请点击这里:offer突击训练营,给你一个offer的保障,求职跳槽的看过来!

相关文章:

单例模式之「双重校验锁」

单例模式之「双重校验锁」 单例模式 单例即单实例,只实例出来一个对象。一般在创建一些管理器类、工具类的时候,需要用到单例模式,比如JDBCUtil 类,我们只需要一个实例即可(多个实例也可以实现功能,但是增…...

2023年中国商业版服务器操作系统市场发展规模分析:未来将保持稳定增长[图]

服务器操作系统一般指的是安装在大型计算机上的操作系统,比如Web服务器、应用服务器和数据库服务器等,是企业IT系统的基础架构平台,也是按应用领域划分的三类操作系统之一。同时服务器操作系统也可以安装在个人电脑上。 服务器操作系统分类 …...

BIM如何通过3D开发工具HOOPS实现WEB轻量化?

随着建筑行业的数字化转型和信息建模技术的不断发展,建筑信息模型(BIM)已经成为设计、建造和管理建筑项目的标准。然而,BIM模型通常包含大量的数据,导致在Web上的传输和查看效率低下。为了解决这一挑战,HOO…...

Unity 3D基础——通过四元数控制对象旋转

在这个例子中,通过键盘的左右方向来控制场景中的球体 Sphere 的横向运动,而 Cube 立方体则会一直朝着球体旋转。 1.在场景中新建一个 Cube 立方体和一个 Sphere 球体,在 Inspector 视图中设置 Cube 立方体的坐标为(3,0…...

python--短路运算,把0、空字符串和None看成 False,其他数值和非空字符串都看成 True

代码 print(3 and 4 and 5) # 5 print(5 and 6 or 7) # 6 4 > 3 and print(‘hello world’) # 输出hello world 注释: 在逻辑运算中,不一定逻辑运算符的两边都是纯表达式。也可以是数值类型的数据。 Python把0、空字符串和None看成 False&#xff…...

《算法通关村第一关——链表青铜挑战笔记》

《算法通关村第一关——链表青铜挑战笔记》 Java如何构造出链表 概念 如何构造出链表,首先必须了解什么是链表! 单向链表就像一个铁链一样,元素之间相互链接,包含多个节点,每个节点有一个指向后继元素的next指针。…...

【深度学习实验】循环神经网络(四):基于 LSTM 的语言模型训练

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. RNN与梯度裁剪 2. LSTM模型 3. 训练函数 a. train_epoch b. train 4. 文本预测 5. GPU判断函数 6. 训练与测试 7. 代码整合 经验是智慧之父,记忆…...

IOS课程笔记[1-3] 第一个IOS应用

安装开发环境 安装Xcode软件 历史版本查找 https://developer.apple.com/download/all/?qdebug 创建Object-C项目 启动过程 步骤 1.加载Main中定义的storyBoard 2.加载Main控制器 3.加载控制器下的View组件显示 获取控件的两种方式 定义属性连线:property (…...

Flink的基于两阶段提交协议的事务数据汇实现

背景 在flink中可以通过使用事务性数据汇实现精准一次的保证,本文基于Kakfa的事务处理来看一下在Flink 内部如何实现基于两阶段提交协议的事务性数据汇. flink kafka事务性数据汇的实现 1。首先在开始进行快照的时候也就是收到checkpoint通知的时候,在…...

树模型(三)决策树

决策树是什么?决策树(decision tree)是一种基本的分类与回归方法。 长方形代表判断模块 (decision block),椭圆形成代表终止模块(terminating block),表示已经得出结论,可以终止运行。从判断模块引出的左右箭头称作为分支(branch)…...

vueday01——使用属性绑定+ref属性定位获取id

1.属性绑定&#xff08;Attribute 绑定&#xff09; 第一种写法 <div v-bind:id"refValue"> content </div> 第二种写法&#xff08;省略掉v-bind&#xff09; <div :id"refValue"> content </div> 2.代码展示 <template…...

LeetCode 260. 只出现一次的数字 III:异或

【LetMeFly】260.只出现一次的数字 III 力扣题目链接&#xff1a;https://leetcode.cn/problems/single-number-iii/ 给你一个整数数组 nums&#xff0c;其中恰好有两个元素只出现一次&#xff0c;其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返…...

使用PyTorch解决多分类问题:构建、训练和评估深度学习模型

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…...

基于nodejs+vue网课学习平台

各功能简要描述如下: 1个人信息管理:包括对学生用户、老师和管理员的信息进行录入、修改&#xff0c;以及老师信息的审核等 2在库课程查询:用于学生用户查询相关课程的功能 3在库老师查询:用于学生用户查询相关老师教学的所有课程的功能。 4在库学校查询:用于学生用户查询相关学…...

读书笔记:Effective C++ 2.0 版,条款13(初始化顺序==声明顺序)、条款14(基类有虚析构)

条款13: 初始化列表中成员列出的顺序和它们在类中声明的顺序相同 类成员是按照它们在类里被声明的顺序进行初始化的&#xff0c;和它们在成员初始化列表中列出的顺序没一点关系。 根本原因可能是考虑到内存的分布&#xff0c;按照定义顺序进行排列。 另外&#xff0c;初始化列表…...

flutter开发实战-下拉刷新与上拉加载更多实现

flutter开发实战-下拉刷新与上拉加载更多实现 在开发中经常遇到列表需要下拉刷新与上拉加载更多&#xff0c;这里使用EasyRefresh&#xff0c;版本是3.3.21 一、什么是EasyRefresh EasyRefresh可以在Flutter应用程序上轻松实现下拉刷新和上拉加载。它几乎支持所有Flutter Sc…...

旧手机热点机改造成服务器方案

如果你也跟我一样有这种想法, 那真的太酷了!!! ok,前提是得有root,不然体验大打折扣 目录 目录 1.做一个能爬墙能走百度直连的热点机(做热点机用) 2.做emby视频服务器 3.做文件服务, 存取文件 4.装青龙面板,跑一些定时任务 5.做远程摄像头监控 6.做web服务器 7.内网穿…...

网工实验笔记:策略路由PBR的应用场景

一、概述 PBR&#xff08;Policy-Based Routing&#xff0c;策略路由&#xff09;&#xff1a;PBR使得网络设备不仅能够基于报文的目的IP地址进行数据转发&#xff0c;更能基于其他元素进行数据转发&#xff0c;例如源IP地址、源MAC地址、目的MAC地址、源端口号、目的端口号、…...

webrtc快速入门——使用 WebRTC 拍摄静止的照片

文章目录 使用 getUserMedia() 拍摄静态照片HTML 标记JavaScript 代码初始化startup() 函数获取元素引用获取流媒体 监听视频开始播放处理按钮上的点击包装 startup() 方法 清理照片框从流中捕获帧 例子代码HTML代码CSS代码JavaScript代码 过滤器使用特定设备 使用 getUserMedi…...

预约按摩app软件开发定制足浴SPA上们服务小程序

同城按摩小程序是一种基于地理位置服务的小程序&#xff0c;它可以帮助用户快速找到附近的按摩师&#xff0c;并提供在线预约、评价、支付等功能。用户可以通过手机或者其他移动设备访问同城按摩小程序&#xff0c;实现足不出户就能预约到专业的按摩服务。 一、同城按摩小程序的…...

shell脚本--常见案例

1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件&#xff1a; 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元&#xff08;GRU&#xff09;长短期记忆神经网络&#xff08;LSTM&#xff09…...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...

网站指纹识别

网站指纹识别 网站的最基本组成&#xff1a;服务器&#xff08;操作系统&#xff09;、中间件&#xff08;web容器&#xff09;、脚本语言、数据厍 为什么要了解这些&#xff1f;举个例子&#xff1a;发现了一个文件读取漏洞&#xff0c;我们需要读/etc/passwd&#xff0c;如…...

Java编程之桥接模式

定义 桥接模式&#xff08;Bridge Pattern&#xff09;属于结构型设计模式&#xff0c;它的核心意图是将抽象部分与实现部分分离&#xff0c;使它们可以独立地变化。这种模式通过组合关系来替代继承关系&#xff0c;从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…...