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

news2024/12/28 21:24:47

文章目录

  • 💡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.coloradmin.cn/o/1486930.html

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

相关文章

【LeetCode】升级打怪之路 Day 11:栈的应用、单调栈

今日题目: Problem 1: 栈的应用 155. 最小栈 | LeetCode20. 有效的括号 | LeetCode150. 逆波兰表达式求值 | LeetCode Problem 2: 单调栈 496. 下一个更大元素 I739. 每日温度503. 下一个更大元素 II 目录 Problem 1:栈 - “先进后出”的应用LC 155. 最…

【Tomcat】The CATALINA_HOME environment variable is not defined correctly

文章目录 一、问题二、解决办法三、优化 一、问题 运行绿色版Tomcat时,单击apache-tomcat-9.0.27\bin\startup.bat时窗口一闪而过。 检查JAVA_HOME环境变量,可以发现并没有问题。 为了检查错误,将startup.bat程序使用文本编辑器打开&#x…

Vue2+ElementUI列表、表格组件的封装

Vue2ElementUI列表组件的封装:引言 在日常开发中,我们经常会遇到需要展示列表数据的场景。ElementUI 提供的 el-table 组件是一个功能强大的表格组件,可以满足大部分的需求。但是,在实际应用中,我们往往需要根据业务需…

Python | Conda安装包报错:PackagesNotFoundError

Conda在下载安装包时报错: PackagesNotFoundError: The following packages are not available from current channels:- XXXXXX(包名)有如下两种解决方法: 方法一:将conda-forge添加到搜索路径上 在命令行运行下方指令…

spring cloud 之 Netflix Eureka

1、Eureka 简介 Eureka是Spring Cloud Netflix 微服务套件中的一个服务发现组件,本质上是一个基于REST的服务,主要用于AWS云来定位服务以实现中间层服务的负载均衡和故障转移,它的设计理念就是“注册中心”。 你可以认为它是一个存储服务地址信息的大本…

14-RPC-自研微服务框架

RPC RPC 框架是分布式领域核心组件,也是微服务的基础。 RPC (Remote Procedure Call)全称是远程过程调用,相对于本地方法调用,在同一内存空间可以直接通过方法栈实现调用,远程调用则跨了不同的服务终端&a…

游戏框架搭建

使用框架的目标:低耦合,高内聚,表现和数据分离 耦合:对象,类的双向引用,循环引用 内聚:相同类型的代码放在一起 表现和数据分离:需要共享的数据放在Model里 对象之间的交互一般有三…

XUbuntu22.04之显示实时网速(二百一十八)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…

【字符串】马拉车(Manacher)算法

本篇文章参考:比较易懂的 Manacher(马拉车)算法配图详解 马拉车算法可以求出一个字符串中的最长回文子串,时间复杂度 O ( n ) O(n) O(n) 因为字符串长度的奇偶性,回文子串的中心可能是一个字符,也可能是…

智慧草莓基地:Java与SpringBoot的技术革新

✍✍计算机毕业编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java、…

ue4.27 发现 getRandomReachedLocation 返回 false

把这个玩意儿删掉,重启工程,即可 如果还不行 保证运动物体在 volum 内部,也就是绿色范围内确保 project setting 里面的 navigation system 中 auto create navigation data 是打开的(看到过博客说关掉,不知道为啥) 如果还不行&…

STM32学习和实践笔记(1): 装好了的keil μVision 5

2019年3月在淘宝上买了这块STM32的开发板,学了一段时间后就丢下了,今天重新捡起来,决定好好学习、天天向上。 对照教程,今天先把keil5装上了。 装的过程有以下几点值得记录下: 1)用注册机时,…

【数据结构】B树

1 B树介绍 B树(英语:B-tree),是一种在计算机科学自平衡的树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成。B树,概括来说是一个一般化的…

波斯猫 6页面 宠物动物 长毛猫 HTML5 带背景音乐 JS图片轮播特效 滚动文字 鼠标经过图片 JS时间代码

波斯猫 6页面 宠物动物 长毛猫 HTML5 带背景音乐 JS图片轮播特效 滚动文字 鼠标经过图片 JS时间代码 注册表单 宠物网页成品 海量学生网页成品 个人博客 人物明星 城市家乡 旅游景点 美食特产 购物电商 公司企业 学校大学 科普教育 宠物动物 鲜花花卉 植物水果 茶叶咖啡 健康生…

【前端寻宝之路】学习如何使用HTML实现简历展示和填写

🌈个人主页: Aileen_0v0 🔥热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​💫个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-iJ3Ou0qMGFVaqVQq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

Google Dremel和parquet的复杂嵌套数据结构表征方法解析

转载请注明出处。作者:archimekai 核心参考文献: Dremel: Interactive Analysis of Web-Scale Datasets 文章目录 引言复杂嵌套数据结构的无损表征问题Dremel论文中提出的表征方法parquet备注 引言 Dremel是Google的交互式分析系统。Google大量采用prot…

LabVIEW石油钻机提升系统数字孪生技术

LabVIEW石油钻机提升系统数字孪生技术 随着数字化、信息化、智能化的发展,石油钻采过程中的石油钻机数字化技术提升成为了提高钻井效率、降低生产成本的重要途径。基于中石油云平台提供的数据,采用数字孪生技术,对石油钻机提升系统进行数字化…

设计模式(十三)抽象工厂模式

请直接看原文:设计模式(十三)抽象工厂模式_抽象工厂模式告诉我们,要针对接口而不是实现进行设计。( )-CSDN博客 -------------------------------------------------------------------------------------------------------------------------------- …

Some collections -- 2024.3

一、TensorFlow Android (dataset: Mnist) We used TensorFlow to define and train our machine learning model, which can recognize handwritten numbers, called a number classifier model in machine learning terminology. We transform the trained TensorFlow mod…

pytest-allure报告生成

pytest生成allure报告步骤: 下载allure,配置allure报告的环境变量:把allure-2.13.7\bin 配置到环境变量path路径 验证:在dos窗口和pycharm窗口分别验证:allure –version 2. 生成临时的json报告 在pytest.ini配置文…