volatile关键字(juc编程)

volatile关键字

3.1 看程序说结果

分析如下程序,说出在控制台的输出结果。

Thread的子类

public class VolatileThread extends Thread {

    // 定义成员变量
    private boolean flag = false ;
    public boolean isFlag() { return flag;}

    @Override
    public void run() {

        // 线程休眠1秒
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 将flag的值更改为true
        this.flag = true ;
        System.out.println("flag=" + flag);

    }
}

测试类

public class VolatileThreadDemo01 {
    
    public static void main(String[] args) {

        // 创建VolatileThread线程对象
        VolatileThread volatileThread = new VolatileThread() ;
        volatileThread.start();

        // 在main线程中获取开启的线程中flag的值
        while(true) {
            System.out.println("main线程中获取开启的线程中flag的值为" + volatileThread.isFlag());
        }
        
    }
}

控制台输出结果

前面是false,过了一段时间之后就变成了true

按照我们的分析,当我们把volatileThread线程启动起来以后,那么volatileThread线程开始执行。在volatileThread线程的run方法中,线程休眠1s,休眠一秒以后那么flag的值应该为

true,此时我们在主线程中不停的获取flag的值。发现前面释放false,后面是true

信息,那么这是为什么呢?要想知道原因,那么我们就需要学习一下JMM。

3.2 JMM

概述:JMM(Java Memory Model)Java内存模型,是java虚拟机规范中所定义的一种内存模型。

Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。

特点:

  1. 所有的共享变量都存储于主内存(计算机的RAM)这里所说的变量指的是实例变量和类变量。不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。

  2. 每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。

  3. 线程对变量的所有的操作(读,写)都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主

    内存完成。

在这里插入图片描述

3.3 问题分析

了解了一下JMM,那么接下来我们就来分析一下上述程序产生问题的原因。

在这里插入图片描述

产生问题的流程分析:

  1. VolatileThread线程从主内存读取到数据放入其对应的工作内存

  2. 将flag的值更改为true,但是这个时候flag的值还没有回写主内存

  3. 此时main线程读取到了flag的值并将其放入到自己的工作内存中,此时flag的值为false

  4. VolatileThread线程将flag的值写回到主内存,但是main函数里面的while(true)调用的是系统比较底层的代码,速度快,快到没有时间再去读取主内存中的值,所以while(true)

    读取到的值一直是false。(如果有一个时刻main线程从主内存中读取到了flag的最新值,那么if语句就可以执行,main线程何时从主内存中读取最新的值,我们无法控制)

我们可以让主线程执行慢一点,执行慢一点以后,在某一个时刻,可能就会读取到主内存中最新的flag的值,那么if语句就可以进行执行。

测试类

public class VolatileThreadDemo02 {

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

        // 创建VolatileThread线程对象
        VolatileThread volatileThread = new VolatileThread() ;
        volatileThread.start();

        // main方法
        while(true) {
            if(volatileThread.isFlag()) {
                System.out.println("执行了======");
            }

            // 让线程休眠100毫秒
            TimeUnit.MILLISECONDS.sleep(100);
        }

    }
}

控制台输出结果

flag=true
执行了======
执行了======
执行了======
....

此时我们可以看到if语句已经执行了。当然我们在真实开发中可能不能使用这种方式来处理这个问题,那么这个问题应该怎么处理呢?我们就需要学习下一小节的内容。

3.4 问题处理

3.4.1 加锁

第一种处理方案,我们可以通过加锁的方式进行处理。

测试类

public class VolatileThreadDemo03 {

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

        // 创建VolatileThread线程对象
        VolatileThread volatileThread = new VolatileThread() ;
        volatileThread.start();

        // main方法
        while(true) {

            // 加锁进行问题处理
            synchronized (volatileThread) {
                if(volatileThread.isFlag()) {
                    System.out.println("执行了======");
                }
            }

        }

    }
}

控制台输出结果

flag=true
执行了======
执行了======
执行了======
....

工作原理说明

对上述代码加锁完毕以后,某一个线程支持该程序的过程如下:

a.线程获得锁

b.清空工作内存

c.从主内存拷贝共享变量最新的值到工作内存成为副本

d.执行代码

e.将修改后的副本的值刷新回主内存中

f.线程释放锁

3.4.2 volatile关键字

第二种处理方案,我们可以通过volatile关键字来修饰flag变量。

线程类

public class VolatileThread extends Thread {

    // 定义成员变量
    private volatile boolean flag = false ;
    public boolean isFlag() { return flag;}

    @Override
    public void run() {

        // 线程休眠1秒
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 将flag的值更改为true
        this.flag = true ;
        System.out.println("flag=" + flag);

    }
}
//--------------------------------更新之后的案例-------------------------------------------
public class VolatileTest extends Thread{
    boolean flag = false;
    int i = 0;

    public void run() {
        while (!flag) {
            i++;
        }
        System.out.println("stope" + i);
    }

    public static void main(String[] args) throws Exception {
        VolatileTest vt = new VolatileTest();
        vt.start();

        Thread.sleep(10);
        vt.flag = true;

    }
}

控制台输出结果

flag=true
执行了======
执行了======
执行了======
....

工作原理说明

在这里插入图片描述

执行流程分析

  1. VolatileThread线程从主内存读取到数据放入其对应的工作内存
  2. 将flag的值更改为true,但是这个时候flag的值还没有回写主内存
  3. 此时main线程读取到了flag的值并将其放入到自己的工作内存中,此时flag的值为false
  4. VolatileThread线程将flag的值写到主内存
  5. main线程工作内存中的flag变量副本失效
  6. main线程再次使用flag时,main线程会从主内存读取最新的值,放入到工作内存中,然后在进行使用

总结: volatile保证不同线程对共享变量操作的可见性,也就是说一个线程修改了volatile修饰的变量,当修改写回主内存时,另外一个线程立即看到最新的值。

​ 但是volatile不保证原子性(关于原子性问题,我们在下面的小节中会介绍)。

volatile与synchronized的区别:

  1. volatile只能修饰实例变量和类变量,而synchronized可以修饰方法,以及代码块。

  2. volatile保证数据的可见性,但是不保证原子性(多线程进行写操作,不保证线程安全);而synchronized是一种排他(互斥)的机制(因此有时我们也将synchronized这种锁称

    之为排他(互斥)锁),synchronized修饰的代码块,被修饰的代码块称之为同步代码块,无法被中断可以保证原子性,也可以间接的保证可见性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

钡铼BL101网关助力智慧城市路灯远程智能管控

在迈向智慧城市的征途中,基础设施的智能化改造是关键一环,而路灯作为城市脉络的照明灯塔,其智能化升级对于节能减排、提升城市管理效率具有重要意义。钡铼BL101网关,作为Modbus转MQTT的专业桥梁,正以其卓越的性能和广泛…

数据仓库与数据库的区别

在数据管理和分析的过程中,我们常常会听到“数据库”和“数据仓库”这两个术语。 虽然它们看起来相似,但实际上它们在设计目的、结构和使用场景上都有显著的区别。 数据库是什么? 数据库(Database)是一个用于存储和管…

[创业之路-120] :全程图解:软件研发人员如何从企业的顶层看软件产品研发?

目录 一、企业全局 二、供应链 三、团队管理 四、研发流程IPD 五、软件开发流程 六、项目管理 七、研发管理者的自我修炼 一、企业全局 二、供应链 三、团队管理 四、研发流程IPD 五、软件开发流程 六、项目管理 七、研发管理者的自我修炼

时空预测 | 基于深度学习的碳排放时空预测模型

时空预测 模型描述 数据收集和准备:收集与碳排放相关的数据,包括历史碳排放数据、气象数据、人口密度数据等。确保数据的质量和完整性,并进行必要的数据清洗和预处理。 特征工程:根据问题的需求和领域知识,对数据进行…

【C++】基础知识--inline(内联)关键字以及与宏的区别

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话: 知不足而奋进,望远山而前行&am…

Nginx Rewrite技术

一:理解地址重写 与 地址转发的含义。二:理解 Rewrite指令 使用三:理解if指令四:理解防盗链及nginx配置 简介:Rewrite是Nginx服务器提供的一个重要的功能,它可以实现URL重定向功能。 一:理解地…

医学图像预处理之z分数归一化

在医学图像处理中,Z分数标准化(Z-score normalization)是一种常用的数据标准化方法,其目的是将数据集中的每个图像像素值转换为具有均值为0和标准差为1的标准化值。这种标准化方法有助于改善图像的质量,便于后续图像处…

RS485中继器的作用你还不知道?

RS485是一种串行通信协议,支持设备间长距离通信。RS485中继器则像“传声筒”,能放大衰减信号,延长通信距离,隔离噪声,扩展分支。在实际场景中,如工厂内,通过中继器可确保控制室与远距离机器间通…

嵌入式Linux 中常见外设屏接口分析

今天将梳理下嵌入式外设屏幕接口相关的介绍,对于一个嵌入式驱动开发工程师,对屏幕都可能接触到一些相关的的调试,这里首先把基础相关的知识梳理。 1. 引言 在嵌入式开发过程中,使用到的液晶屏有非常多的种类,根据不同技术和特性分类,会接触到TN液晶屏,TN液晶屏 VA液晶屏…

Java基础16(集合框架 List ArrayList容器类 ArrayList底层源码解析及扩容机制)

目录 一、什么是集合? 二、集合接口 三、List集合 四、ArrayList容器类 1. 常用方法 1.1 增加 1.2 查找 int size() E get(int index) int indexOf(Object c) boolean contains(Object c) boolean isEmpty() List SubList(int fromindex,int …

H3C防火墙抓包(图形化)

一.报文捕获 ,然后通过wireshark查看报文 二.报文示踪 , 输入源目等信息, 查看报文的详情

鸿蒙Harmony实战—通过登录Demo了解ArkTS

ArkTS是HarmonyOS优选的主力应用开发语言。ArkTS围绕应用开发在TypeScript(简称TS)生态基础上做了进一步扩展,继承了TS的所有特性,是TS的超集。 ArkTS在TS的基础上主要扩展了如下能力: 基本语法:ArkTS定义…

今天碰到一个gitee的严重问题

今天碰到一个gitee的严重问题 今天访问gitee的官网,无法访问… 代码无法提交 接下来 接下来 gitee的客服给我说 不知道哪天会不会代码直接没了 不知道哪天会不会代码直接没了

大模型应用场景在哪?探索人工智能的无限可能

随着人工智能技术的飞速发展,大模型在自然语言处理、计算机视觉、推荐系统等领域取得了显著成果。这些大模型,如OpenAI的GPT-3、谷歌的BERT、百度的ERNIE等,不仅在学术界引起了巨大反响,也在产业界得到了广泛应用。本文将以大模型…

JavaSE 面向对象程序设计 正则表达式

正则表达式 正则表达式(Regular Expression,简称Regex)是用于匹配文本中模式的字符串表达式。它由普通字符(例如字母、数字)和特殊字符(称为元字符)组成,可以非常灵活地定义搜索模式…

哔哩哔哩视频URL解析原理

哔哩哔哩视频URL解析原理 视频网址解析视频的原理通常涉及以下几个步骤: 1、获取视频页面源代码:通过HTTP请求获取视频所在网页的HTML源代码。这一步通常需要处理反爬虫机制,如验证码或用户登录。 2、解析页面源代码:分析HTML源代…

性能工具之 JMeter 常用组件介绍(七)

文章目录 一、后置处理器1、Regular Expression Extractor(正则表达式提取器)2、JSON Extractor(JSON表达式提取器)3、Regular Expression Extractor(正则表达式提取器) 二、小结 本文主要介绍JMeter主流后置处理器的功能 一、后置处理器 从上面可以看出后置处理可以插件挺多&a…

探索Linux命令的新利器:linux-command

在Linux操作系统中,熟练掌握各种命令是成为一名高效开发者或管理员的关键。然而,即使是经验丰富的用户,有时也会遇到命令用法不熟悉或者记忆模糊的情况。这时,一个功能强大的命令搜索工具就显得格外重要。最近在逛github的时候正好…

充电学习— 9、Typec Pd

GND:线缆接地 TX RX:数据流data传输,支持2.0 3.0 speed兼容 VBUS:线缆cable电源,bus power CC:电缆cable的连接、方向、角色检测和当前模式的配置通道; 有emark时, 一个成为VCONN&am…

数据结构——队列(Queue)详解

1.队列(Queue) 1.1概念 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)的性质 入队列:进行插入操作的一端称为队尾(Tail/Rear) 出…