JUC并发编程之volatile详解

news2024/11/18 17:19:22

目录

1. volatile

1.1 volatile关键字的作用

1.1.1 变量可见性

1.1.2 禁止指令重排序

1.2 volatile可见性案例

1.3 volatile非原子性案例

1.4 volatile 禁止重排序

1.5 volatile 日常使用场景

送书活动


1. volatile

在并发编程中,多线程操作共享的变量时,可能会导致线程安全问题,如数据竞争、可见性问题等。为了解决这些问题,Java提供了JUC(java.util.concurrent)工具包,其中包含了很多用于处理并发编程的工具类和接口。在JUC中,volatile是一个关键字,它可以用于修饰变量,用来确保变量的可见性和禁止指令重排序,从而在一定程度上解决线程安全问题。

1.1 volatile关键字的作用

1.1.1 变量可见性

在多线程环境下,如果一个线程修改了共享变量的值,其他线程可能由于线程间的数据不一致性而看不到该变量的最新值。这种问题称为“变量不可见性”或“可见性问题”。

volatile关键字可以确保被修饰的变量对所有线程可见。当一个线程修改了volatile变量的值,其他线程立即能够看到修改后的最新值,而不会使用缓存中的旧值。

1.1.2 禁止指令重排序

在JVM(Java虚拟机)中,为了优化性能,编译器和处理器可能会对指令进行重排序。在单线程环境下,这种重排序不会影响程序的执行结果。然而,在多线程环境下,指令重排序可能会导致线程安全问题。

volatile关键字可以防止指令重排序,确保被修饰的变量按照代码中的顺序执行。

1.2 volatile可见性案例

public class VolatileExample {
    private static volatile boolean flag = false;

    public static void main(String[] args) {
        new Thread(() -> {
            while (!flag) {
                System.out.println("Waiting for the flag to be true...");
            }
            System.out.println("Flag is now true. Exiting the thread.");
        }).start();

        try {
            Thread.sleep(1000); // 确保主线程在子线程之前执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Setting the flag to true...");
        flag = true;
    }
}

在上面的示例中,我们创建了一个VolatileExample类,并声明了一个volatile类型的flag变量。在主线程中,我们启动一个新的子线程,该子线程会不断地检查flag变量的值,直到flag变为true时,子线程退出。

在主线程中,我们将flag变量设置为true。由于flag变量被声明为volatile类型,子线程能够及时看到flag的最新值,从而退出循环,输出“Flag is now true. Exiting the thread.”。

这个示例演示了volatile关键字的作用,确保了flag变量的可见性。如果我们没有使用volatile关键字,子线程可能会一直循环下去,因为它看不到主线程对flag的修改。

1.3 volatile非原子性案例

public class Test {


    public static void main(String[] args) throws InterruptedException {
        VolatileAtomicityExample example = new VolatileAtomicityExample();
        for(int i=1;i<=100;i++){
            new Thread(()->{
                for(int j=1;j<=1000;j++)
                    example.increment();
            },String.valueOf(i)).start();
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println(example.getCount());


    }

}
class VolatileAtomicityExample {
    volatile int count = 0;

    public  void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

我们创建了一个VolatileAtomicityExample类,其中的成员变量count被声明为volatile类型。然后,我们创建了100个线程,每个线程分别执行1000次increment()操作,对count进行自增。最后,我们在主线程中打印count的最终值。以上示例中的输出结果可能会因为运行时的不确定性而有所不同。每次运行时可能得到不同的结果,但通常结果都小于100000。为了解决这个问题,我们需要使用synchronized关键字或其他线程安全机制来确保increment()方法的原子性。

这是什么原因呢?

 对于volatile变量具备可见性,JVM只是保证从主内存加载到线程工作内存的值是最新的,也仅是数据加载时是最新的。但是多线程环境下,"数据计算"和"数据赋值"操作可能多次出现,若数据在加载之后,若主内存volatile修饰变量发生修改之后,线程工作内存中的操作将会作废去读主内存最新值,操作出现写丢失问题。即各线程私有内存和主内存公共内存中变量不同步,进而导致数据不一致。由此可见volatile解决的是变量读时的可见性问题,但无法保证原子性,对于多线程修改主内存共享变量的场景必须使用加锁同步。

1.4 volatile 禁止重排序

内存屏障是一种硬件指令或者编译器指令,用于控制内存操作的顺序,以确保多线程环境下的内存可见性和正确的执行顺序。

内存屏障分为两种类型:

  1. 内存读屏障(Load Barrier):它是一个特殊的硬件或者编译器指令,用于保证在内存读取操作之前,所有的先行写操作都已经完成,并且其结果对当前线程可见。也就是说,读屏障可以防止后续读取指令重排序到读屏障之前的位置。

  2. 内存写屏障(Store Barrier):它是一个特殊的硬件或者编译器指令,用于保证在内存写入操作之前,所有的先行写操作和写屏障之前的写操作都已经完成,并且其结果对其他线程可见。也就是说,写屏障可以防止前面的写入指令重排序到写屏障之后的位置。

volatile关键字通过内存屏障来保证变量的读写操作不会被重排序。具体来说,对于volatile变量的写操作,在写入变量之后会插入写屏障,这样可以防止其他指令重排序到写屏障之前。类似地,对于volatile变量的读操作,在读取变量之前会插入读屏障,这样可以防止其他指令重排序到读屏障之前。

通过这种方式,volatile关键字确保了对变量的读写操作具有一定的有序性,从而保证了多线程环境下的内存可见性和正确的执行顺序。

1.5 volatile 日常使用场景

状态标志:当一个线程修改了某个状态标志,其他线程需要立即看到最新的状态。这时可以使用volatile关键字修饰状态标志,保证其在多线程之间的可见性。例如:

public class Task implements Runnable {
    private volatile boolean isRunning = true;

    @Override
    public void run() {
        while (isRunning) {
            // 执行任务逻辑
        }
    }

    public void stop() {
        isRunning = false;
    }
}

双重检查锁定(Double-Checked Locking):在多线程环境下,当需要延迟初始化一个对象时,为了避免重复初始化,常常使用双重检查锁定。在这种情况下,需要使用volatile关键字来确保对象在多线程环境中的可见性。例如:

public class Singleton {
    private volatile static Singleton instance;

    private Singleton() {}

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

在上述代码中,我们实现了一个简单的单例模式。在getInstance()方法中,我们使用双重检查锁定来实现延迟初始化,确保只有在instance为空时才创建新的Singleton实例。

然而,在多线程环境下,由于指令重排序的存在,可能会导致以下问题:

  1. 对象引用不为空但尚未初始化:在线程A执行完instance = new Singleton();这一行之前,可能发生指令重排序,导致instance的引用不为空,但是Singleton实例的初始化还未完成。这样,线程B在执行return instance;时就会得到一个尚未初始化的对象,导致错误。

  2. 可见性问题:指令重排序也可能导致线程B无法及时看到线程A的初始化操作。例如,线程A对instance的赋值可能被重排到线程A的后面执行,从而线程B在读取instance时得到一个旧的引用,无法感知线程A的初始化操作。

为了解决这个问题,需要在创建Singleton实例时使用volatile关键字来保证对象的可见性和禁止指令重排序。

送书活动

《硅基物语·我是灵魂画手》

当AI遇上绘画,会打开怎样的奇妙世界?

用ChatGPT+Midjourney西出人类的灵魂与梦想

用StableDiffusion+D-ID画出青春绚丽的渴望

激活每个人隐藏的绘画天赋

人人都能成为顶尖绘画大师

ChatGPT+Midjourncy+StableDiffusion+D-ID+RunwayGen-l

爆火软件全流程协作

掌据AI绘商技巧

解锁超全绘画关键司

讲解创作底层逻辑

一本书讲透AI绘画全流程

内容简介

一本将AI绘画讲透的探秘指南,通过丰富的实践案例操作,通俗易懂地讲述AI绘画的生成步骤生动展现了AI绘画的魔法魅力。从历史到未来,跨越百年时空,从理论到实践,讲述案例操作:从技术到哲学,穿越多个维度,从语言到绘画,落地实战演练。AI绘画的诞生,引发了奇点降临,点亮了AGI(通用人工智能),并涉及 Prompt、风格,技术细节、多模态交互,AIGC等一系列详细讲解。让您轻松掌握生图技巧,创造出独特的艺术作品,书写属于自己的艺术时代。

 当当网链接:http://product.dangdang.com/29601870.html

 关注博主、点赞、收藏、

评论区评论

  即可参与送书活动! 

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

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

相关文章

SAP MIRO 报错 Allowed posting periods:xx xxx

背景&#xff1a;在八月初&#xff0c;七月份财务账期没关&#xff0c;七月份物料帐期已关 用户在做MIRO的时候&#xff0c;无法开票成功&#xff0c;报错&#xff1a; Allowed posting periods:xx xxx 但是集团要求&#xff0c;这些帐应该记在七月份 查询相关资料得知。MI…

PLUS模型 | 历史土地利用数据,进行多情景模式下的未来土地利用预测

工业革命以来&#xff0c;社会生产力迅速提高&#xff0c;人类活动频繁&#xff0c;此外人口与日俱增对土地的需求与改造更加强烈&#xff0c;人-地关系日益紧张。此外&#xff0c;土地资源的不合理开发利用更是造成了水土流失、植被退化、水资源短缺、区域气候变化、生物多样性…

【嵌入式学习笔记】嵌入式入门3——串口

1.数据通信的基础概念 1.1.串行/并行通信 数据通信按数据通信方式分类&#xff1a;串行通信、并行通信 1.2.单工/半双工/全双工通信 数据通信按数据传输方向分类&#xff1a;单工通信、半双工通信、全双工通信 单工通信&#xff1a;数据只能沿一个方向传输半双工通信&…

支持中文创成式填充 AI版PS 2023 v25.0安装教程

抖音保姆级视频教程: https://v.douyin.com/iJdUjg2o/ PS 2023 v25.0安装包地址&#xff1a; 链接: https://pan.baidu.com/s/1PXgVHDHdMIRcDzV4IfGAQw?pwd2023 提取码: 2023 如有疑问请加交流请加QQ群&#xff1a;814894746 安装教程总结&#xff1a; 卸载之前的PS beta版…

C++11 通用工具

通用工具 目录 pair和tuple智能指针数值极值type trait 和type utility辅助函数clock和timerbitset随机数 1 pair和Tuple 1.1 pair 头文件 #include<utility>pair定义 pair<string,string> author{James","joyce"};)] --> pair操作 1.2 tup…

阿里云 MSE + ZadigX ,无门槛实现云原生全链路灰度发布

作者&#xff1a;ZadigX 企业发布现状痛点 目前企业在选择和实施发布策略时面临以下困境&#xff1a; 1. 缺乏云原生能力&#xff1a; 由于从传统部署转变为云原生模式后&#xff0c;技术架构改造需要具备相关能力的人才。这使得企业在发布策略方面难以入手。 2. 缺乏自动化…

一张图看懂应用程序访问驱动的内部原理

咱就浅谈一下字符设备驱动的内部实现~ 1、当我们在应用程序中使用open打开文件的时候&#xff0c;会自动在/dev/mycdev下生成一个inode号。 2、只要文件存在于系统中&#xff0c;在系统内核就会存在一个inode结构体&#xff0c;里面存储文件的一些相关信息&#xff0c;其中信息…

如何使用STAR原则优化项目管理?

介绍STAR原则 1.1 STAR原则的定义 STAR原则是一个行为面试技术&#xff0c;即Situation&#xff08;情境&#xff09;、Task&#xff08;任务&#xff09;、Action&#xff08;行动&#xff09;和Result&#xff08;结果&#xff09;。这种原则被广泛应用在职业面试中&#x…

【严重】Metabase 基于H2引擎的远程代码执行漏洞

漏洞描述 Metabase 是一个开源的数据分析和可视化工具。 由于 CVE-2023-38646 的补丁(从H2 JDBC连接字符串中删除INIT脚本以防止命令注入)修复不完全&#xff0c;Metabase 仍受到命令注入的影响。攻击者可使用 H2 作为数据库引擎&#xff0c;通过 /api/setup/validate 端点发…

idea设置项目使用本地仓库

方法一&#xff1a;最简单有效的方法 在pom加入以下内容 <repositories><repository><id>local-repo</id><url>file://本地仓库目录</url></repository></repositories>方法二&#xff1a;idea设置maven为workoffline&#x…

局域网内电脑ping不通(防火墙惹的祸)

明明是同一网段&#xff08;同一局域网&#xff09;的电脑&#xff0c;却ping不通&#xff0c;这种情况大概率就是防火墙惹得祸了。除了把防火墙关掉&#xff0c;我们还可以采取如下解决方案。 解决方案&#xff1a; 1. 打开&#xff1a;控制面板\系统和安全\Windows Defende…

真香!云原生安全,大客户终于愿意买单了

5年前&#xff0c;我的哥们老丁 从倒腾防火墙转行卖新产品 据说那是一片蓝海&#xff0c;美其名曰 「云原生安全」 可是&#xff0c;“吆喝”了一段时间 老丁发现&#xff0c;这个蓝海没想象那么蓝 很多老客户都不买账 相反还是那些传统安全产品销量更好 为啥客户不感冒呢…

程序员生涯写过最大的Bug!这威力多少有点大了...

程序员一生与bug奋战&#xff0c;可谓是杀敌无数&#xff0c;见怪不怪了&#xff01;如果要问码农们&#xff0c;在自己职业生涯中写过最大的Bug是什么&#xff0c;想必每一位都需要一个话筒来说上几分钟...... 一个BUG&#xff0c;连升两级 一个bug能给程序员带来什么?优秀员…

Vue2(初识vue)

目录 一&#xff0c;Vue2简介1.1&#xff0c;什么是vue1.2&#xff0c;初始vue1.3&#xff0c;搭建vue环境1.4&#xff0c;第一个hello world 二&#xff0c;基础知识2.1 指令2.2-1 指令v-text2.2-2 指令v-html2.2-3 指令v-if2.2-4 指令v-else2.2-5 指令v-show2.2-6 v-if指令与…

IDEA导入第三方jar包, 并在Maven中打包该jar包

IDEA导入jar包, 并在Maven中打包该jar包 背景步骤IDEA导入jar包Maven中打包第三方jar包 背景 项目因为某些原因, 需要引入demo中代码, 但是demo使用的是 java而且没有使用任何框架, 并且还引用第三方 jar, 因此在自己项目(SpringBootMaven)中集成demo时. 整个集成过程中颇为麻…

2023拒绝内卷!两年转行网络安全真实看法!

我目前转行网络安全两年&#xff0c;没啥天分&#xff0c;全靠努力&#xff0c;基本能够得上中级的水平了。看到大家对转行网络安全挺感兴趣&#xff0c;也有挺多争议&#xff0c;想把我的建议和经验告诉大家。 有很多人觉得网络安全已经饱和了&#xff0c;现在选择这个工作&a…

docker-compose 搭建 Sharding-Proxy 5.4.0 分库分表代理服务

感谢: 程序员一枚 提供搭建方式 项目地址: https://gitee.com/dromara/RuoYi-Cloud-Plus/tree/2.X/ 1.在 mysql 创建两个库 创建两个库 data-center_0 data-center_1 分别执行如何sql CREATE TABLE t_order_0 (order_id bigint(20) UNSIGNED NOT NULL COMMENT 主键ID,user_…

【数据结构】27.移除元素

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

P4145 上帝造题的七分钟 2 / 花神游历各国

上帝造题的七分钟 2 / 花神游历各国 题目背景 XLk 觉得《上帝造题的七分钟》不太过瘾&#xff0c;于是有了第二部。 题目描述 "第一分钟&#xff0c;X 说&#xff0c;要有数列&#xff0c;于是便给定了一个正整数数列。 第二分钟&#xff0c;L 说&#xff0c;要能修改…

Ubuntu22.04搭建mysql主从复制

记录哈&#xff0c;这个需要两台服务器&#xff0c;每台服务器上面都要安装mysql&#xff0c;若是没有安装&#xff0c;请分别在两台服务器上面运行下面的命令 sudo apt install mysql-server 引用 Ubuntu下的mysql8.0忘记密码怎么办? 环境 Ubuntu 22.04 主机:192.168.164.1…