【Java多线程编程】解决线程的不安全问题之volatile关键字

news2025/1/21 1:00:03

目录

1. 造成线程不安全的代码

2. volatile能保证内存可见性

3. synchronized与volatile的区别

3.1 synchronized能保证原子性

3.2 volatile不能保证原子性


1. 造成线程不安全的代码

有一代码,要求两个线程运行。并自定义一个标志位 flag,当线程2(thread2)修改标志位后,线程1(thread1)结束执行。如下代码所示:

public class TestDemo3 {
    public static int flag = 0;//自定义一个标志位
    public static void main(String[] args) {
        Thread thread1 = new Thread(()-> {
            while (flag == 0) {
                //空
            }
            System.out.println("thread1线程结束");
        });//线程1
        
        Thread thread2 = new Thread(()-> {
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入一个整数:");
            flag = scanner.nextInt();
        });//线程2
        
        thread1.start();//启动线程1
        thread2.start();//启动线程2
    }
}

运行后打印:

预期效果为:thread1 中的 flag==0 作为条件进入 while 循序,thread2 中通过 scanner 输入一个非 0 的值,从而使得 thread1 线程结束。

实际效果:thread2 中输入非 0 数后,光标处于闪烁状态代表循环未结束。

造成程序没有达到如期效果的原因是内存的不可见性导致 while 条件判断始终发生错误。因此,我们得使用 volatile 关键字来保证内存的可见性,使得 while 条件判断能够正常识别修改后的标志位 flag。


2. volatile能保证内存可见性

可见性指一个线程对共享变量值的修改,能够及时地被其他线程看到。而 volatile 关键字就保证内存的可见性。在上述代码中标志位 flag 未使用 volatile 修饰导致 while 循环不能正确判断,其原因如下:

flag == 0这个判断,会实现两条操作:第一条,load 从内存读取数据到 cpu的 寄存器。第二条,cmp 比较寄存器中的值是否为0,是则返回 true 否则返回 false。

但是,编译器有一个特性:优化。优化什么呢?由于进行大量数据操作时 load 的开销很大,编译器就做出了一个优化,就是无论数据大或小 load 操作只会执行一次。

因此,flag == 0 这个条件第一作为 load 加载到了寄存器中,后序无论对 flag 进行怎样的修改 cmp 比较的时候始终为 true 了。

这就是多线程运行时,编译器对于代码进行优化操作的内存不可见性。也就是内存看不到实际的情况。因此,我们只需要在 flag 前面加上 volatile 关键字使得编译器不对 flag 进行优化,这样就能达到效果。如下代码所示:

public class TestDemo3 {
    volatile public static int flag = 0;//volatile修饰自定义标志位
    public static void main(String[] args) {
        Thread thread1 = new Thread(()-> {
            while (flag == 0) {
                //空
            }
            System.out.println("thread1线程结束");
        });//线程1

        Thread thread2 = new Thread(()-> {
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入一个整数:");
            flag = scanner.nextInt();
        });//线程2

        thread1.start();//启动线程1
        thread2.start();//启动线程2
    }
}

运行后打印:

通过上述代码及打印结果,可以看到达到了预期效果。因此,被 volatile 修饰的变量能够保证每次从内存中重新读取数据。

解释内存可见性:

thread1频繁读取主内存,效率比较第,就被优化成直接读直接的工作内存

thread2修改了主内存的结果,由于thread1没有读主内存,导致修改不能被识别 

上述的工作内存理解为CPU寄存器,主内存理解为内存。 


3. synchronized与volatile的区别


3.1 synchronized能保证原子性

何为原子性在这篇文章有详细介绍:synchronized关键字

以下代码的需求为:两个线程分别计算10000 次,使得 count 总数达到 20000:

//创建一个自定义类
class myThread {
    int count = 0;
    public void run() {
        synchronized (this){
            count++;
        }
    }
    public int getCount() {
        return count;
    }
}
public class TreadDemo1 {
    public static void main(String[] args) throws InterruptedException {
        myThread myThread = new myThread();//实例化这个类
 
        Thread thread1 = new Thread(()-> {
            for (int i = 0; i < 10000; i++) {
                myThread.run();
            }
        });
        Thread thread2 = new Thread(()-> {
            for (int i = 0; i < 10000; i++) {
                myThread.run();
            }
        });
        thread1.start();//启动线程thread1
        thread2.start();//启动线程thread2
        thread1.join();//等待线程thread1结束
        thread2.join();//等待线程thread2结束
        System.out.println(myThread.getCount());//获取count值
    }
}

运行后打印:


3.2 volatile不能保证原子性

当我们把上述代码中的 run 方法去掉 synchronized 的关键字,再给 count 变量加上 volatile 关键字。

//创建一个自定义类
class myThread {
    volatile int count = 0;
    public void run() {
       count++;
    }
    public int getCount() {
        return count;
    }
}

运行后打印:


🧑‍💻作者:一只爱打拳的程序猿,Java领域新星创作者,阿里云社区优质创造者。

🗃️文章收录于:Java多线程编程

🗂️JavaSE的学习:JavaSE

🗂️Java数据结构:数据结构与算法

 

本期博文到这里就结束了,感谢点赞、评论、收藏、关注~

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

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

相关文章

UVM学习——搭建简单的UVM平台

引言 本专栏的博客均与 UVM 的学习相关&#xff0c;学习参考&#xff1a; 【1】UVM Tutorial 【2】张强著&#xff0c;UVM实战 &#xff08;卷 Ⅰ&#xff09; 【3】Download UVM (Standard Universal Verification Methodology) 本专栏的学习基本依照 资料【2】的主线&#…

【机器学习】正规方程法求解线性回归问题

前情提要&#xff1a;https://blog.csdn.net/weixin_45434953/article/details/130604086 正规方程 正规方程能以更好的方式求得假设函数中 θ \theta θ的最优值。它提供了一种用于求 θ \theta θ的解析方法&#xff0c;而不是梯度下降那样的迭代方法。也就是只需要一次运算…

Microsoft Power BI连接本地mysql 数据库 !power bi提示此连接器需要安装一个或多个其他组件才能使用怎么办!

一、步骤 &#xff08;一&#xff09;从菜单栏点击进入mysql数据库 点击主页>获取数据>更多 选择mysql数据库&#xff0c;点击连接 &#xff08;二&#xff09;已经安装了mysql connector/net还是提示此连接器需要安装一个或多个其他组件才能使用-解决 重装了几次都…

vue-cli 关闭 Uncaught error 的全屏提示

在使用vue-cli开发项目的时候&#xff0c;如果代码抛出异常了&#xff0c;那么就会出现一个全屏的提示框&#xff0c;长下面这样&#xff1a; 经过一段时间的排查发现是webpack的问题&#xff0c;排查方式就是打开控制台&#xff0c;看这个框的一些属性&#xff0c;通常会有一些…

【DNDC模型】在土地利用变化、未来气候变化下的建模方法及温室气体时空动态模拟实践技术

DNDC&#xff08;Denitrification-Decomposition&#xff0c;反硝化-分解模型&#xff09;是目前国际上最为成功的模拟生物地球化学循环的模型之一&#xff0c;自开发以来&#xff0c;经过不断完善和改进&#xff0c;从模拟简单的农田生态系统发展成为可以模拟几乎所有陆地生态…

界面开发框架Qt新手入门教程 - 可编辑树模型的示例(一)

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 The Qt Company是Di…

考研日语-详解ている、てある、ていく、てくる用法

目录 一、ている用法 1. 表示现在状态 2. 表示持续动作 3. 表示经验或习惯 4. 表示结果或效果 二、てある用法 1. 表示已经完成的动作 2. 表示现在状态 3. 表示被动 三、ていく用法 1. 表示未来的动作 2. 表示逐渐变化的过程 四、てくる用法 1. 表示过去到现在的…

Python实现哈里斯鹰优化算法(HHO)优化XGBoost分类模型(XGBClassifier算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 2019年Heidari等人提出哈里斯鹰优化算法(Harris Hawk Optimization, HHO)&#xff0c;该算法有较强的全…

滴滴一面:BigKey问题很致命,如何排查和处理?

说在前面 在40岁老架构师 尼恩的读者社区(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如极兔、有赞、希音、百度、网易、滴滴的面试资格&#xff0c;遇到一几个很重要的面试题&#xff1a; 致命的的Redis BigKey 如何排查&#xff0c;你处理过吗&#xff1f; 与之类似…

Yield Guild Games: RON 质押来啦!

Yield Guild Games (YGG) 自 2022 年 7 月以来一直是 Ronin 区块链的验证者&#xff0c;在保障和维护网络方面发挥着至关重要的作用。随着 2023 年 4 月委托权益证明 (DPoS) 的推出&#xff0c;质押生态系统进一步民主化&#xff0c;允许更多的参与者在赚取奖励的同时为网络的安…

LabVIEWCompactRIO 开发指南19 原始以太网(TCP/UDP)

LabVIEWCompactRIO 开发指南19 原始以太网&#xff08;TCP/UDP&#xff09; TCP和UDP是所有以太网标准的低级构建块。原始TCP和UDP工具在几乎所有编程环境中都得到原生支持&#xff0c;包括LabVIEW。它们提供较低级别的通信功能&#xff0c;这些功能更灵活&#xff0c;但用户…

3.34 haas506 2.0开发教程-example -TFT显示自动生成的二维码

TFT显示自动生成的二维码 应用场景案例说明1.硬件2.连线图 代码源码链接[TFT显示二维码](https://www.yuque.com/haas506/wiki/pubazmzgrf30zws0) 应用场景 二维码在各个领域中的应用越来越广泛&#xff0c;其中一些主要应用场景包括&#xff1a; 电子商务&#xff1a;通过二维…

RHCSA之查看命令帮助手册

目录 RHCSA之查看命令帮助手册 查看命令类型 --- type Linux中对应的命令类型 帮助命令 help 命令 用法1 help 内部命令 用法2 命令 --help 命令的部分语法符号解析 man 命令 man命令用法 man的帮助级 man 命令帮助信息界面中的常用操作 man命令中帮助信息的结构以及意义…

CGAN(条件GAN)

相比于GAN&#xff0c;CGAN给生成器和辨别器都添加了一个辅助信息&#xff0c;假设为y,y可以是标签类别或者其他模态的信息。 目标函数相比于GAN在输入端的x和z变为在y条件下生成的x和z。 模型框架可以表示为&#xff1a; 代码&#xff1a; import argparse import os os.en…

如何在linux中配置JDK环境变量

在linux系统部署皕杰报表&#xff0c;因皕杰报表是一款纯java报表工具&#xff0c;运行时需要jre环境&#xff0c;所以要在服务器上配置三个jdk环境变量path、classpath、JAVA_HOME。 那么为什么要配置jdk环境变量呢&#xff1f;因为java软件运行时要用到一些java命令&#xff…

人体样本? 一站式医学微生态研究解决方案来啦!

细菌&#xff1f;真菌&#xff1f;古菌&#xff1f;病毒&#xff1f; 还在为人体微生态研究选择哪个切入点而苦苦纠结吗&#xff1f; 数据&#xff1f;算法&#xff1f;作图&#xff1f;分析&#xff1f; 还在苦于已有的分析内容脱离医学临床实际而不知所措吗&#xff1f; …

vue 3.0 静态路由配置

今天研究了一下vue3.0的静态路由配置&#xff0c;分享一下。 首先我们现在项目中建立router文件夹&#xff0c;如下图所示&#xff1a; 404文件夹存放404页面&#xff0c;components文件夹存放首页界面&#xff0c;config为设置文件夹&#xff0c;diz存放具体的业务逻辑和界面…

SpringMVC 执行流程

视图阶段&#xff08;老旧JSP等) DispatcherServlet&#xff1a;接收请求、响应结果&#xff0c;所有的请求都要经过它&#xff0c;它是被Tomcat容器初始化的当这个类加载时会加载一些组件类HandlerMapping、HandlerAdapter、ViewResolver等等。 HandlerMapping&#xff1a;根…

考古:Transformer

论文1: 《Attention is all you need》 模型体系结构 Encoder 将符号表示的输入序列 ( x 1 , . . . , x n ) (x_1,...,x_n) (x1​,...,xn​)映射到连续表示的序列 z ( z 1 , . . . , z n ) z(z_1,...,z_n) z(z1​,...,zn​)。 给定 z z z&#xff0c;Encoder 然后一次产生一…

网络安全合规-个人信息安全影响评估

信息安全技术个人信息安全影响评估指南》 一、个人信息安全影响评估定义 个人信息安全影响评估Personal Information Security Impact Assessment&#xff0c;针对个人信息处理活动&#xff0c;检验其合法合规程度&#xff0c;判断其对个人信息主体合法权益造成损害的各种风险…