并发编程三大特性之可见性

news2024/11/27 12:56:44

一、什么是可见性?

               可见性问题是基于CPU位置出现的,cpu处里速度非常快,相对CPU来说去主内存

      获取数据这个事情太慢了,CPU就提供了 L1,L2,L3的三季缓存,每次去主内存拿完

      数据后,数据就先存储到三级缓存,然后cpu再去三级缓存取数据,效率肯定会提升;

      三级缓存就是是每个线程的工作内存,是相互独立的。

              这就带来了一个问题:现在CPU都是多核,每个线程的工作内存(CPU三级缓存)都

      是独立的,会告知每个线程做修改时,只修改自己的工作内存,数据没有及时同步到主内存

      ,从而导致数据不一致的问题

     线程运行时数据处里过程如下:

             

     使用下边代码来验证数据可见性的问题,代码如下:

             

             

二、解决可见性问题的方式

       1、volatile 

             volatile是一个关键字,用于修饰成员变量

              如果属性被volatile修饰,相当于告诉cpu,对于当前属性的操作,不允许使用CPU

              缓存(即线程私有内存),必须去操作主内存。

              volatile的内存语义:

                    (1)volatile 属性被写:当写一个volatile变量,JMM会将当前线程的CPU缓存的

                                 数据及时刷新到主内存中

                    (2)volatile 属性被读:当读一个volatile变量,JMM会将当前线程对应的CPU缓存

                                 设置为无效,必须从主内存读取数据。

               其实变量加了volatile就是告诉cpu,对当前变量的读写操作,不允许使用CPU缓存;加

               了volatile 的变量会在编译成汇编之后追加一个lock前缀,CPU执行这个指令时,如果

                带有lock前缀会做2件事:

                       (1)将当处理器缓存行的数据写回到主内存

                       (2)这个写会的数据,在其他的CPU内核的缓存中,直接无效

                  总结:volatile 就是让CPU每次操作这个数据时,必须立即同步到主内存,以及从主内

                             存读取数据。

                             在代码中若先对volatile属性进行操作,则其他属性也是可见性的。

               针对上边的代码,采用volatile解决可见性问题实现如下:

/*******************************************************
 * 验证线程的可见性问题
 * 每个线程都有自己的私有内存,相互独立,线程运行时处里的是自己私有内存的数据
 *
 * 使用volatile解决内存可见性
 *******************************************************/
public class Test02 {

    private volatile static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            while (flag){

            }
            System.out.println("子线程 t1 中 flag改变");
        });

        t1.start();
        //预期:flag修改成false后,不影响线程t1的运行
        Thread.sleep(100);
        //修改flag 的值
        flag = false;
        System.out.println(" main 线程中修改 flag = false");
    }
}


//方案二:在代码中若先对volatile属性进行操作,则其他属性也是可见性的
public class Test02 {

    private volatile static int i= 0;
    private  static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            while (flag){
                //todo: 在代码中若先对volatile属性进行操作,则其他属性也是可见性的
                i++;
            }
            System.out.println("子线程 t1 中 flag改变");
        });

        t1.start();
        //预期:flag修改成false后,不影响线程t1的运行
        Thread.sleep(100);
        //修改flag 的值
        flag = false;
        System.out.println(" main 线程中修改 flag = false");
    }
}

       2、synchronized

              synchronized也是可以解决可见性问题。

              synchronized内存语义:

                      如果涉及到了synchronized 的同步代码块或者同步方法,获取资源之后,将内部

                      涉及到的变量从CPU缓存(线程私有内存)中移除,必须重新去主内存中取数据;

                      而且在释放锁之后,会立即将CPU缓存中的数据同步到主内存中。

              使用 synchronized 解决内存可见性 示例代码如下:

                           

/*******************************************************
 * 验证线程的可见性问题
 * 每个线程都有自己的私有内存,相互独立,线程运行时处里的是自己私有内存的数据
 *
 * 使用synchronized解决内存可见性
 *******************************************************/
public class Test03 {

    private  static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            while (flag){
                /*
                 * 问题:为什么这里synchronized 放在while循环里边,不放在外边?
                 *     因为线程 在获取到 synchronized 锁之后才会从主内存拿数据,若把synchronized 放在while外边
                 *     则只会从主内存拿一次数据,后边不能监听到变量 flag 变化
                 */
                synchronized (Test03.class){
                    //todo
                }

            }
            System.out.println("子线程 t1 中 flag改变");
        });

        t1.start();
        //预期:flag修改成false后,不影响线程t1的运行
        Thread.sleep(100);
        //修改flag 的值
        flag = false;
        System.out.println(" main 线程中修改 flag = false");
    }
}

       3、Lock

             Lock锁保证可见性的方式和synchronized 完全不同,synchronized是基于内存语义在获取

             锁和释放锁对CPU缓存做一个同步到主内存的操作。

             lock 锁是基于volatile实现的,lock 锁内部进行加锁和释放锁时,会对一个volatile修饰的属

             state做加减操作。

             如果对volatile属性进行写操作,CPU会执行带有lock前缀的指令,会将CPU缓存的数据立

             即同步到主内存,同时也会将其他非volatile属性页一起同步到主内存。还会将其他CPU缓

             存行 中这个volatile数据设置为无效,必须从主内存重新拉取。

             lock解决可见性示例代码如下:

                  

/*******************************************************
 * 验证线程的可见性问题
 * 每个线程都有自己的私有内存,相互独立,线程运行时处里的是自己私有内存的数据
 *
 * 使用lock解决内存可见性
 *******************************************************/
public class Test04 {

    private  static boolean flag = true;
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            while (flag){

                lock.lock();
                try {
                    //。。。。。
                }finally {
                    lock.unlock();
                }

            }
            System.out.println("子线程 t1 中 flag改变");
        });

        t1.start();
        //预期:flag修改成false后,不影响线程t1的运行
        Thread.sleep(100);
        //修改flag 的值
        flag = false;
        System.out.println(" main 线程中修改 flag = false");
    }
}

       4、final

             final本质上说并不能像synchronized和volatile 那种形式保证可见性,final修饰的属性在

             运行期间是不允许修改的,这样一来就间接保证了可见性。

             final与volatile不允许同时修饰一个属性,final修饰的属性不允许被修改,而volatile保证

             每次从主内存读取数据,并且volatile会影响一定性能,就不需要同时修饰。

              final与volatile同时修饰属性会报错,如下图所示:

                   

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

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

相关文章

SIT1051AQ5V 供电,IO 口兼容 3.3V,±58V 总线耐压,CAN FD 静音模式总线收发器

SIT1051AQ 是一款应用于 CAN 协议控制器和物理总线之间的接口芯片,可应用于车载、工业 控制等领域,支持 5Mbps 灵活数据速率 CAN FD ,具有在总线与 CAN 协议控制器之间进行差分信 号传输的能力。 SIT1051AQ 为 SIT1051Q 芯片的…

python应用-计算两个日期的时间差

学习目录 1. 安装deteutil包 2. 导入relativedelta类 3. 计算两个日期的差值 4. 计算1个日期和时间差相加后得到新的日期 之前在工作中遇到一个使用场景:需要计算两个日期之前的差值,比如相差了几年几月几日,查找资料发现deteutil包的rel…

基于Leaflet.js的Marker闪烁特效的实现-模拟预警

目录 前言 一、闪烁组件 1、关于leaflet-icon-pulse 2、 使用leaflet-icon-pulse 3、方法及参数简介 二、闪烁实例开发 1、创建网页 2、Marker闪烁设置 3、实际效果 三、总结 前言 在一些地质灾害或者应急情况当中,或者热门预测当中。我们需要基于时空位置来…

行云防水堡-打造企业数据安全新防线

企业数据安全,顾名思义就是通过各种手段或者技术或者工具保障企业数据的安全性;保障数据信息的硬件、软件及数据受到保护,不受偶然的或者恶意的原因而遭到破坏、更改、泄露,系统连续可靠正常地运行,信息服务不中断。目…

[C++][算法基础]合并集合(并查集)

一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。 现在要进行 m 个操作,操作共有两种: M a b,将编号为 a 和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操…

数据库讲解---(SQL语句--表的使用)【MySQL版本】

零.前言 数据库讲解(MySQL版)(超详细)【第一章】-CSDN博客 数据库-ER图教程_e-r图数据库-CSDN博客 数据库讲解(MySQL版)(超详细)【第二章】【上】-CSDN博客 一.SQL概述 1.1SQL简…

FaceForensics++数据库下载(一步步解析过程)

FaceForensics数据库下载(超详细版教程) 相信很多做deepfake相关研究的朋友,在对模型进行测试或者对潜前人的研究进行复现时,都需要下载一系列数据库并进行预处理等操作,而FaceForensics数据库是一个由数千个使用不同…

地又接错了?又冒烟了吧?

原文来自微信公众号:工程师看海,与我联系:chunhou0820 看海原创视频教程:《运放秘籍》 大家好,我是工程师看海,原创文章欢迎点赞分享! 作为一名硬件工程师,理解地的概念是至关重要的…

大数据之搭建Hive组件

声明:所有软件自行下载,并存放到统一目录中 1.Hive组件的安装配置 1.1实验环境 服务器集群3 个以上节点,节点间网络互通,各节点最低配置:双核 CPU、8GB 内存、100G 硬盘运行环境CentOS 7.4服务和组件完成前面章节实验…

redis的简单操作

redis中string的操作 安装 下载可视化软件:https://gitee.com/qishibo/AnotherRedisDesktopManager/releases。 Mac安装redis: brew install redisWindows安装redis: 安装包下载地址:https://github.com/tporadowski/redis/releases 1.…

内存管理new and delete(C++)

在本篇中,将会较为详细的介绍在 Cpp 中的两个新操作符 new 和 delete,将会介绍其中的底层原理,以及这两个操作符的使用方法。其中还介绍了 new/delete 操作符使用的细节,还扩展了一些有关定位 new 表达式的知识点。最后总结了 mal…

C++练级之路——类和对象(上)

1、类的定义 class 类名{//成员函数 //成员变量}; class为定义的关键字,{ }内是类的主体,注意后面的 ; 不要忘了 类体中的内容成为类的成员,类中的变量为成员变量或类的属性,类中的函数为成员函数或类的方法, 类的两种…

Prompt最佳实践|大模型也喜欢角色扮演?

在OpenAI的官方文档中已经提供了Prompt Enginerring的最佳实践,目的就是帮助用户更好的使用ChatGPT 编写优秀的提示词我一共总结了9个分类,本文讲解第2个分类:要求模型扮演角色 提供更多的细节要求模型扮演角色使用分隔符指定任务步骤提供样…

OPC UA遇见chatGPT

最近opc 基金会将召开一个会议,主题是”OPC UA meets IT“。由此可见,工业自动化行业也开始研究和评估chatGPT带来的影响了。 本文谈谈本人对OPC UA 与chatGPT结合的初步实验和思考。 构建OPC UA 信息模型 chatGPT 的确非常强大了,使用自然…

前端开发之el-table(vue2中)固定列fixed滚动条被固定列盖住

固定列fixed滚动条被固定列盖住 效果图前言解决方案 效果图 前言 在使用fixed固定列的时候会出现滚动条被盖住的情况 解决方案 改变el-table固定列的计算高度即可 .el-table {.el-table__fixed-right,.el-table__fixed {height:auto !important;bottom:15px !important;}}

安装cuda后只在root用户下可见,非root不可见问题

0. 安装cuda和nvidia driver步骤可以参考这篇: https://blog.csdn.net/mygugu/article/details/137474101?spm1001.2014.3001.5502 1.问题记录: 这里记录下安装cuda后遇到的一个奇葩问题,因为安装过程需要root权限,安装后发现…

k8s部署efk

环境简介: kubernetes: v1.22.2 helm: v3.12.0 elasticsearch: 8.8.0 chart包:19.10.0 fluentd: 1.16.2 chart包: 5.9.4 kibana: 8.2.2 chart包:10.1.9 整体架构图: 一、Elasticsearch安装…

Git入门实战教程之创建版本库

一、Git简介 Git是一个分布式版本控制系,分层结构如下: Git分为四层: 1、工作目录 当前正在工作的项目的实际文件目录,我们执行命令git init时所在的地方,也就是我们执行一切文件操作的地方。 2、暂存区 暂存区是…

字符串2s总结

4.字符串 字符串理论基础 什么是字符串 字符串是若⼲字符组成的有限序列,也可以理解为是⼀个字符数组,但是很多语⾔对字符串做了特殊的规定,接下来我来说⼀说C/C中的字符串。 在C语⾔中,把⼀个字符串存⼊⼀个数组时&#xff0c…

前端开发学习笔记 3 (Chrome浏览器调试工具、Emmet语法、CSS复合选择器、CSS元素选择模式、CSS背景)

文章目录 Chrome浏览器调试工具Emmet语法CSS复合选择器后代选择器子选择器并集选择器伪类选择器 CSS元素选择模式元素选择模式概述CSS块标签CSS行内标签CSS行内块标签CSS元素显示模式转换 CSS背景CSS背景颜色CSS背景图片CSS背景图片平铺CSS背景图片位置CSS背景图片固定CSS背景复…