volatile关键字的作用 以及 单例模式(饿汉模式与懒汉模式的区别及改进)

文章目录

  • 💡volatile保证内存可见性
  • 💡单例模式
  • 💡饿汉模式
  • 💡懒汉模式
  • 💡懒汉模式多线程版
  • 💡volatile防止指令重排序

💡volatile保证内存可见性

Volatile 修饰的变量能够保证“内存可见性”以及防止”指令重排序“

什么是可见性:当某个线程修改了某个共享变量,其他的线程是否可以看见修改后的内容;

因为访问一个变量时,CPU就会先把变量从内存中读出来,然后放到CPU寄存器中进行运算;运算完之后,再将新的数据在内存中进行刷新;

在这里插入图片描述

对于操作系统来讲,读内存的速度是比较慢的,(注意:这里的慢 是 相对于寄存器而言的,就像,读内存要比读硬盘快上千倍或上万倍,读寄存器比读内存快上千倍上万倍), 这时候就会影响执行的效率。为了提高效率,编译器就会对代码进行一个优化,把读内存的操作优化成读寄存器,从而减少对内存的读取,提高整个效率;

举个例子:

代码目的:创建两个线程,通过线程2修改线程1的循环判断条件来终止线程1的循环执行

public class Demo1 {private static int flag = 0;public static void main(String[] args) {Thread thread1 = new Thread(() -> {while(flag == 0) {//当循环不等于0时,一直循环,直到flag被改变}System.out.println("thread1 执行结束");});Thread thread2 = new Thread(() -> {Scanner in = new Scanner(System.in);System.out.println("更改flag:");//通过更改flag终止线程1的执行flag = in.nextInt();System.out.println("输入成功");});thread1.start();thread2.start();}
}

在这里插入图片描述

根据结果可以看到,线程1并没有终止循环,这就是“内存可见性”所导致的线程不安全👇

在这里插入图片描述

在多线程的环境下(在单线程环境下没问题),如果编译器作出优化,可能就会导致bug,虽然提高了效率,但是最后结果却是错误的,

此时就需要程序员使用Volatile关键字告诉编译器,不需要进行代码优化:

直接给flag加上Volatile即可

在这里插入图片描述

注意, volatile只能够保证内存可见性问题,不会保证代码的原子性,但是Synchronized既可以保证内存可见性,也能保证原子性;

以上就是volatile能够保证内存可见性的讲解

💡单例模式

单例模式是一种经典的设计模式了,它的作用就是保证在有些场景下,需要一个类只能有一个对象,而不能有多个对象,比如像你以后娶媳妇,你娶媳妇肯定是只能娶一个,而不能娶两个;

但是,问题来了,一个类只需要一个对象,那在new对象的时候只new一次对象不就可以了么,为什么还要弄个这么麻烦的东西呢?

因为啊,只new一次对象确实是只有一个,但是呢,如果你在写代码的过程中忘了呢,然后又new了一次,这种概率是很大的,毕竟,人是最不靠谱的动物😅,就像是有一句话说的好:宁可相信世界上有鬼,也不要相信男人的那张嘴😂,所以的,为了防止这种失误发生,就有了单例模式,在Java中也有许多类似的机制,比如final,就会保证修饰的变量肯定是不能改变的;@override,保证你这方法肯定是一个重写方法;这些都是在语法方面进行了一些限制,但是,在语法方面,对于单例并没有特定的语法,所以,这里就通过编程技巧来达到类似的限制效果;

单例模式的两种实现方式:

💡饿汉模式

1.在类中实例化类的对象,给外界提供一个方法来使用这个对象;

2.将构造方法用private修饰,保证在类外不能再实例化这个类对象

public class SingleTon {//在类的内部实例化对象public static SingleTon instance = new SingleTon();//定义一个方法,用来获取这个对象//后序如果类外的代码想要使用对象时,直接调用这个方法即可public static SingleTon getInstance() {return instance;}//设置一个私有的构造方法,保证在这个类外无法实例化这个对象private SingleTon(){}
}

在这里插入图片描述

可以看到,这里的对象被static修饰,所以在类被加载的时候创建,创建的时机就比较早,并且被static修饰的对象只会被创建一次,所以这种在类加载时就创建实例的模式称为饿汉模式

💡懒汉模式

懒汉模式单线程版:

这样的写法与上面的相同点就是:同样在类外不能再第二次实例化对象,不同点是:将创建对象的时机放在getInstance方法中,这样在类加载的时候就不会创造实例,而是当第一次调用这个方法时才会去创建

public class SingleTon {public static SingleTon instance = null;//定义一个方法,用来获取这个对象//后序如果类外的代码想要使用对象时,直接调用这个方法即可public static SingleTon getInstance() {//懒汉模式if(instance == null) {instance = new SingleTon();}return instance;}//设置一个私有的构造方法,保证在这个类外无法实例化这个对象private SingleTon(){}
}

在这里插入图片描述

💡懒汉模式多线程版

在线程安全方面,上面的饿汉模式是在多线程下是安全的,而懒汉模式在多线程下是不安全的;

因为,如果多个线程同时访问一个变量,那么不会出现不安全问题,如果多个线程同时修改一个变量,就有可能出现不安全问题;

饿汉模式下,只进行了访问,没有涉及到修改

在这里插入图片描述

懒汉模式下,不仅进行了访问,还涉及了修改,那么下面就讲解以下懒汉模式在多线程下如何会产生不安全

在这里插入图片描述

在这里插入图片描述

既然出现了不安全问题,那么如何将懒汉模式修改成安全的呢?

💡方法:进行加锁,使线程安全

在这里插入图片描述

但是,如果锁加在这个地方,仍然是不安全的,因为,这样还是会进行穿插执行,如果两个并发的进入的 if 语句中,那么,就会进行锁竞争,假设,thread1 获取到了锁,thread2 在阻塞等待,等到 thread1 创建一次对象,释放锁后,thread2 就又会载获取到锁,进行创建对象,所以,这个加锁操作并没有保证它是一个整体(非原子性)

在这里插入图片描述

所以说,并不是加了锁就安全,只有锁加对了才会安全,在加锁的时候要保证以下几方面:

  1. 锁的 {} 的范围是合理的,能够把需要作为整体的每个部分都包括进去;

  2. 锁的对象能够起到锁竞争的效果;

懒汉模式多线程版改进👇

将if语句和new都放在锁里面成为一个整体,这样就避免了会穿插执行;

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

但是上述代码还有一个问题,每当调用getInstance时,都会尝试去进行加锁,而加锁是一个开销很大的操作,而懒汉模式之所以会出现线程不安全问题,是因为只是在第一次调用getInstance方法new对象时,可能会出现问题,但是,只要new完对象以后,就不用再进行锁竞争了,直接访问就可以了,所以再次进行优化👇:

    public static SingleTon getInstance() {//在最外面在进行一次判断if(instance == null) {synchronized (SingleTon.class) {if(instance == null) {instance = new SingleTon();}}}return instance;}

在第一次实例化对象后,以后再调用个getInstance方法时,就不会再创建对象,而且也不会再去获取锁,因为,第一个if判断语句都不会进去,所以不会执行到加锁的语句;

上面的单例模式看着好像是完全没问题了,但是,还是有一个问题,就是可能会触发指令重排序问题,所以就需要使用volatile解决指令重排序问题

💡volatile防止指令重排序

指令重排序:编译器会保证在你代码逻辑不变的情况下,对代码进行优化,使代码的性能得到提高,这样的操作称为指令重排序;

举个例子:

在这里插入图片描述

在这里插入图片描述

在代码中,在实例化对象这一步可能会出现指令重排序问题,下面就来讲解一下为什么👇

在这里插入图片描述

在这里插入图片描述

对于上述的指令重排序问题,解决方案就是:使用volatile关键字修饰singleTon

**线程安全的单例模式(懒汉模式)**👇

public class SingleTon {//使用volatile关键字修饰,防止指令重排序public static volatile SingleTon singleTon = null;public static SingleTon getSingleTon() {if(singleTon == null) {synchronized (SingleTon.class) {if(singleTon == null) {singleTon = new SingleTon();}}}return singleTon;}private SingleTon() {};}

💡💡这里再次提醒,使用单例模式要注意三个要点:

  • 加锁
  • 两层if判断
  • 使用volatile修饰引用,防止指令重排序

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决连接工具Mobaxterm连接错误Connection timed out----VMware里Linux端口号固定

错误:Connection timed out 原因: 没有固定ip,网络断开,再次连接就是新的IP 解决: 再次测试连接,弹出这个就代表成功了

CUDA 中的线程组织

明朝那些事中有一句话:我之所以写徐霞客是想告诉你,所谓千秋霸业万古流芳,与一件事相比,其实都算不了什么,这件事情就是——用你喜欢的方式度过一生。 我们以最简单的 CUDA 程序:从 GPU 中输出 Hello World…

实现一个简单的哈希映射功能

说在前面 🎈哈希表大家应该都经常用到吧,那么大家有没有想过哈希表是怎么实现的呢?今天让我们一起从一道简单的题目来初步了解一个哈希表的简单原理。 目的 不使用任何内建的哈希表库设计一个哈希映射(HashMap)。 实…

bert 相似度任务训练完整版

任务 之前写了一个相似度任务的版本:bert 相似度任务训练简单版本,faiss 寻找相似 topk-CSDN博客 相似度用的是 0,1,相当于分类任务,现在我们相似度有评分,不再是 0,1 了,分数为 0-5,数字越大…

EasyRecovery易恢复2024免费文件数据恢复软件下载

一、软件概述 EasyRecovery易恢复中文文件数据恢复软件是一款专为中文用户设计的强大数据恢复工具。该软件致力于帮助用户从各种存储设备中恢复因各种原因丢失的中文文件,如文档、图片、视频、音频等。凭借其核心技术和多年的研发经验,EasyRecovery易恢…

C语言计算误码率

#include <stdio.h> #include <stdlib.h> bool dayintrue; //是否打印 int main(){ int i,k,g0; int n10,n20; int good0,bad0; double rate; (dayin)? printf("打印具体数据\n"):printf("不打印具体数据\n\n");…

STM32-SPI通信协议

串行外设接口SPI&#xff08;Serial Peripheral Interface&#xff09;是由Motorola公司开发的一种通用数据总线。 在某些芯片上&#xff0c;SPI接口可以配置为支持SPI协议或者支持I2S音频协议。 SPI接口默认工作在SPI方式&#xff0c;可以通过软件把功能从SPI模式切换…

Python·算法·每日一题(3月4日)最长公共前缀

题目 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 “”。 示例 示例 1&#xff1a; 输入&#xff1a;strs ["flower","flow","flight"] 输出&#xff1a;"fl"示例 2&#xff1a;…

【数据结构与算法】常见排序算法(Sorting Algorithm)

文章目录 相关概念1. 冒泡排序&#xff08;Bubble Sort&#xff09;2. 直接插入排序&#xff08;Insertion Sort&#xff09;3. 希尔排序&#xff08;Shell Sort&#xff09;4. 直接选择排序&#xff08;Selection Sort&#xff09;5. 堆排序&#xff08;Heap Sort&#xff09;…

【脑科学相关合集】有关脑影像数据相关介绍的笔记及有关脑网络的笔记合集

【脑科学相关合集】有关脑影像数据相关介绍的笔记及有关脑网络的笔记合集 前言脑模板方面相关笔记清单 基于脑网络的方法方面数据基本方面 前言 这里&#xff0c;我将展开有关我自己关于脑影像数据相关介绍的笔记及有关脑网络的笔记合集。其中&#xff0c;脑网络的相关论文主要…

【错误处理】【Hive】【Spark】ERROR FileFormatwriter: Aborting job null.

问题背景 近日&#xff0c;使用 Spark 在读写 Hive 表时发生了报错&#xff1a;Aborting job null&#xff0c;如果怎么都使用不了那张表的话&#xff0c;大概率是那张表有脏数据&#xff0c;导致整张表无法正常使用。 ERROR FileFormatwriter: Aborting job null.解决方法 …

SpringBoot 如何快速过滤出一次请求的所有日志?

前言 在现网出现故障时&#xff0c;我们经常需要获取一次请求流程里的所有日志进行定位。如果请求只在一个线程里处理&#xff0c;则我们可以通过线程ID来过滤日志&#xff0c;但如果请求包含异步线程的处理&#xff0c;那么光靠线程ID就显得捉襟见肘了。 华为IoT平台&#x…

《自然》:人工智能在创造性思维方面超越人类

发散性思维被认为是创造性思维的指标。ChatGPT-4 在三项有151名人类参与的**发散思维测试中&#xff0c;**展现出比人类更高水平的创造力&#xff0c;结果显示人工智能在创意领域持续发展。 发散性思维的特点是能够针对没有预期解决方案的问题提出独特的解决方案&#xff0c;例…

TOMCAT的安装与基本信息

一、TOMCAT简介 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;是开发和调试JSP 程序的首选。对于一个初学者来说&#xff0c;可以这样认为&#xff0c…

IO 与 NIO

优质博文&#xff1a;IT-BLOG-CN 一、阻塞IO / 非阻塞NIO 阻塞IO&#xff1a;当一条线程执行read()或者write()方法时&#xff0c;这条线程会一直阻塞直到读取到了一些数据或者要写出去的数据已经全部写出&#xff0c;在这期间这条线程不能做任何其他的事情。 非阻塞NIO&…

记录踩过的坑-macOS下使用VS Code

目录 切换主题 安装插件 搭建Python开发环境 装Python插件 配置解释器 打开项目 打开终端 切换主题 安装插件 方法1 方法2 搭建Python开发环境 装Python插件 配置解释器 假设解释器已经通过Anaconda建好&#xff0c;只需要在VS Code中关联。 打开项目 打开终端

ArmV8架构

Armv8/armv9架构入门指南 — Armv8/armv9架构入门指南 v1.0 documentation 上面只是给了一个比较好的参考文档 其他内容待补充

网络-httpclient调用https服务端绕过证书的方法

httpclient调用https服务端绕过证书的方法 在日常开发或者测试中&#xff0c;通常会遇到需要用httpclient客户端调用对方http是服务器的场景&#xff0c;由于没有证书&#xff0c;所以直接是无法调用的。采用下面的方法可以绕过证书验证&#xff1a; TrustManager[] trustAll…

AutoSAR(基础入门篇)13.5-Mcal Mcu时钟的配置

目录 一、EB的Mcu模块结构 二、时钟的配置 对Mcu的配置主要就是其时钟树的配置,但是EB将GTM、ERU等很多重要的模块也都放在了Mcu里面做配置,所以这里的Mcu是一个很庞大的模块, 我们目前只讲时钟树的部分 一、EB的Mcu模块结构 1. 所有的模块都基本上是这么些配置类别,Mc…

单词级文本攻击—论文阅读

TAAD2.2论文概览 0.前言1-101.Bridge the Gap Between CV and NLP! A Gradient-based Textual Adversarial Attack Frameworka. 背景b. 方法c. 结果d. 论文及代码 2.TextHacker: Learning based Hybrid Local Search Algorithm for Text Hard-label Adversarial Attacka. 背景b…