Java 多线程之 volatile(可见性/重排序)

news2024/9/29 19:18:57

文章目录

    • 一、概述
    • 二、使用方法
    • 三、测试程序
      • 3.1 验证可见性的示例
      • 3.2 验证指令重排序的示例

一、概述

  • 在Java中,volatile 关键字用于修饰变量,其作用是确保多个线程之间对该变量的可见性禁止指令重排序优化

  • 当一个变量被声明为volatile时,线程在读取和写入该变量时会直接操作主内存中的值,而不会使用线程自己的工作内存。这意味着当一个线程修改了一个volatile变量的值时,其他线程将立即看到这个变化,而不会使用缓存中的旧值。底层原理应该是实现了多CPU缓存一致性协议(如MESI),保证了线程可见性。如下图

    在这里插入图片描述

  • volatile关键字还可以防止指令重排序优化。在多线程环境下,为了提高执行效率,编译器和处理器可能会对指令进行重排序。然而,这种重排序可能会导致多线程程序出现意想不到的结果。通过使用volatile关键字,可以确保特定操作的执行顺序与程序中的顺序一致,从而避免了指令重排序可能引发的问题。底层原理应该是使用了屏障(如loadfence、storefence原语指令),禁止指令重排序。

  • volatile 关键字只能保证单个变量的原子性操作和可见性,并不能替代synchronized关键字或Lock接口来实现更复杂的操作。如果需要进行复合操作,例如原子性的读取-修改-写入操作,仍然需要使用synchronized关键字或Lock接口来保证线程安全性。如 x = y++; 则需要使用 synchronized 将整个语句加锁。

二、使用方法

  • 使用时方法简单,直接在变量定义时添加 volatile 关键字即可,如下

    volatile int count1 = 1;
    private volatile int count2 = 2;
    volatile boolean flag1 = false;
    private volatile boolean flag2 = false;
    

三、测试程序

3.1 验证可见性的示例

  • 在下面示例中,Counter 类有一个 count 变量用于计数,如果不使用 volatile 关键字修饰。在 increment 方法中,两个线程分别对 count 进行自增操作。然后在 Main 类的 main 方法中,创建了两个线程并启动它们,每个线程分别对 Counter 对象的 count 执行1000次自增操作。

  • 由于没有使用 volatile 关键字,线程在执行自增操作时,会将 count 的值从主内存复制到各自的线程工作内存中,进行自增操作后再将结果写回主内存。这可能导致一个线程对 count 的修改无法被另一个线程立即感知到,从而导致计数不准确。

  • 因此,当运行示例时,输出的最终计数结果可能小于2000,因为两个线程之间的自增操作并没有得到正确同步和可见性保证。

  • 相反,如果给 count 变量上添加 volatile 关键字修饰符,可以确保线程之间对该变量的读写操作具有可见性和一致性,从而解决问题。

    package top.yiqifu.study.p004_thread;
    
    
    public class Test051_VolatileVisible {
    
        public static class Counter {
            // 不使用 volatile 关键字
            private int count = 0;
    
            // 使用 volatile 关键字
            // private volatile int count = 0;
    
    
            public void increment() {
                count++;
            }
    
            public int getCount() {
                return count;
            }
        }
    
        public static void main(String[] args) {
            Counter counter = new Counter();
    
            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    counter.increment();
                }
            });
    
            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    counter.increment();
                }
            });
    
            thread1.start();
            thread2.start();
    
            try {
                thread1.join();
                thread2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("最终结果: " + counter.getCount());
        }
    
    }
    
    

3.2 验证指令重排序的示例

  • 在下面示例中,Test052_VolatileReorderingExample 类有一个 writer 方法和一个 reader 方法。

    • 在 writer 方法中,首先对变量 x 赋值为1,然后将 flag 设置为 true。
    • 在 reader 方法中,如果 flag 的值为 true,则打印变量 x 的值。
  • 创建一个测试方法 test,在这个方法中创建了两个线程,一个线程执行 writer 方法,另一个线程执行 reader 方法。然后在 main 方法中,创建一个线程用死循环去执行他。

  • 由于变量x和flag没有使用 volatile 关键字,编译器和处理器可能会对指令进行重排序。在不保证顺序性的情况下,可能会发生以下两种重排序情况:

    • 写入操作的重排序:编译器和处理器可能会将写操作2(flag = true)重排序到写操作1(x = 1)之前。
    • 读取操作的重排序:编译器和处理器可能会将读操作2(int a = x)重排序到读操作1(if (flag))之前。
  • 这种重排序可能导致在 reader 方法中打印的变量 a 的值为0,即使在 writer 方法中已经将其设置为1。这是因为在没有足够同步保证的情况下,读操作可能先于写操作执行。

  • 要解决这个问题,可以通过在 x 和 flag 变量上添加 volatile 关键字修饰符,可以防止指令重排序,从而避免这种问题。

  • 下面是测试程序,我在测试时执行了35万次时出现了指令重排序,出现这个问题的概念不是固定的,您测试时需要耐心等待。

    • Test052_VolatileReorderingExample.java 文件内容

      package top.yiqifu.study.p004_thread;
      
      public class Test052_VolatileReorderingExample {
      
          // 不使用 volatile 关键字
          private int x = 0;
          private boolean flag = false;
      
      //        // 使用 volatile 关键字
      //        private volatile int x = 0;
      //        private volatile boolean flag = false;
      
          public void writer() {
              x = 1;          // 写操作1
              flag = true;    // 写操作2
          }
      
          public void reader() {
              if (flag) {     // 读操作1
                  int a = x;  // 读操作2
                  if(a == 0) {
                      System.out.println("出现了指令重排序,说明先执行了 flag = true,  x = 1 还没有执行");
                  }
              }
          }
      
      }
      
      
    • 测试类 Test052_VolatileOrder.java 内容

      package top.yiqifu.study.p004_thread;
      
      public class Test052_VolatileOrder {
      
          private static void test(){
              Test052_VolatileReorderingExample example = new Test052_VolatileReorderingExample();
      
              Thread thread1 = new Thread(() -> {
                  example.writer();
              });
      
              Thread thread2 = new Thread(() -> {
                  example.reader();
              });
      
              thread1.start();
              thread2.start();
      
              try {
                  thread1.join();
                  thread2.join();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      
          public static void main(String[] args) {
      
              Thread thread = new Thread(()->{
                  long count = 0;
                  while (true){
                      test();
                      Thread.yield();
                      count++;
                      if(count%10000 == 0){
                          System.out.println("主线程还活着,已执行"+count+"次");
                      }
                  }
              });
              thread.start();
      
              try {
                  thread.join();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      
      }
      
      

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

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

相关文章

暴力求解欲哭无泪之保安问题

身为程序员哪一个瞬间让你最奔溃&#xff1f; > 提醒&#xff1a;在发布作品前&#xff0c;请把不需要的内容删掉。 方向一&#xff1a;身为程序员遇到过的奔溃瞬间 写题目想到第一个方法便是暴力求解,然后少情况 题目如下: 方向二&#xff1a;如何解决遇到的奔溃瞬间 不…

Arduino驱动Si7021温湿度传感器(温湿度传感器)

目录 1、传感器特性 2、控制器和传感器连线图 3、驱动程序 Si7021温湿度传感器,应用了专用的数字模块采集技术和温湿度传感技术,具有极高的可靠性与卓越的长期稳定性。同时其体积小巧、精度高,特别是拥有毫秒级测试转换时间(DHT系列需要约2s的转换时间),启动测量与读…

2023.11.19使用flask制作一个文件夹生成器

2023.11.19使用flask制作一个文件夹生成器 实现功能&#xff1a; &#xff08;1&#xff09;在指定路径上建立文件夹 &#xff08;2&#xff09;返回文件夹的路径和建立成功与否的提示 main.py import os from flask import Flask, request, jsonify, render_templateapp F…

生态系统NPP及碳源、碳汇模拟实践技术应用

由于全球变暖、大气中温室气体浓度逐年增加等问题的出现&#xff0c;“双碳”行动特别是碳中和已经在世界范围形成广泛影响。碳中和可以从碳排放&#xff08;碳源&#xff09;和碳固定&#xff08;碳汇&#xff09;这两个侧面来理解。陆地生态系统在全球碳循环过程中有着重要作…

微信关键词自动回复有什么用?

微信关键词自动回复有什么用&#xff1f; 关键词回复可以帮助解答客户的高频次问题。 假如&#xff0c;微信可以设置自动回复。。。 你还在担心一个个通过好友手动发欢迎语吗&#xff1f; 遇到常规问题&#xff0c;不用再复制粘贴那个已经回答了一百遍的答案吗&#xff1f;…

小诺2.0开源版工程启动

小诺是一款开源的前后端开发框架&#xff0c;同若依、SpringBladex一样可作为私活、外包脚手架。 开源地址&#xff1a;Snowy: 最新&#xff1a;&#x1f496;国内首个国密前后分离快速开发平台&#x1f496;&#xff0c;采用Vue3AntDesignVue3 ViteSpringBootMpHuToolSaToke…

【链表的说明、方法---顺序表与链表的区别】

文章目录 前言什么是链表链表的结构带头和不带头的区别 链表的实现&#xff08;方法&#xff09;遍历链表头插法尾插法任意位置插入一个节点链表中是否包含某个数字删除链表某个节点删除链表中所有关键字key清空链表所有节点 ArrayList 和 LinkedList的区别总结 前言 什么是链…

Week-T10 数据增强

文章目录 一、准备环境和数据1.环境2. 数据 二、数据增强&#xff08;增加数据集中样本的多样性&#xff09;三、将增强后的数据添加到模型中四、开始训练五、自定义增强函数六、一些增强函数 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f…

数据结构(c语言版) 树的遍历

作业要求 以如下图为例&#xff0c;完成树的遍历&#xff1a; 1、利用孩子兄弟表示法的存储结构 2、利用先根序列创建树 3、先根遍历树 4、后根遍历树 思考 预期的结果应该为&#xff1a; 1、先根创建树时需要输入的数据为&#xff1a; A B E 0 F 0 0 C 0 D G 0 0 0 0 2、…

第3关:图的广度优先遍历

任务要求参考答案评论2 任务描述相关知识编程要求测试说明 任务描述 本关任务&#xff1a;以邻接矩阵存储图&#xff0c;要求编写程序实现图的广度优先遍历。 相关知识 广度优先遍历类似于树的按层次遍历的过程。 假设从图中某顶点v出发&#xff0c;在访问了v之后依次访问…

基于Vue+SpringBoot的考研专业课程管理系统

项目编号&#xff1a; S 035 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S035&#xff0c;文末获取源码。} 项目编号&#xff1a;S035&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 考研高校模块2.3 高…

多目标应用:基于非支配排序的鲸鱼优化算法NSWOA求解微电网多目标优化调度(MATLAB代码)

一、微网系统运行优化模型 微电网优化模型介绍&#xff1a; 微电网多目标优化调度模型简介_IT猿手的博客-CSDN博客 二、基于非支配排序的鲸鱼优化算法NSWOA 基于非支配排序的鲸鱼优化算法NSWOA简介&#xff1a; 三、基于非支配排序的鲸鱼优化算法NSWOA求解微电网多目标优化…

设计模式-解析器-笔记

“领域规则”模式 在特定领域中&#xff0c;某些变化虽然频繁&#xff0c;但可以抽象为某种规则。这时候&#xff0c;结合特定领域&#xff0c;将稳日抽象为语法规则&#xff0c;从而给出在该领域下的一般性解决方案。 典型模式&#xff1a;Interpreter 动机(Motivation) 在…

测试用例的8大设计原则

我们看到的大部分关于测试用例设计的文章&#xff0c;都在讲等价类、因果图、流程法等内容&#xff0c;这是关于测试用例的具体设计方法层面。本文想讨论的重点是&#xff0c;测试用例设计该遵循什么原则&#xff0c;有哪些思维和观点有助于产出更好的测试设计&#xff0c;这些…

ChatGLM2 大模型微调过程中遇到的一些坑及解决方法(更新中)

1. 模型下载问题 OSError: We couldnt connect to https://huggingface.co to load this file, couldnt find it in the cached files and it looks like bert-base-uncased is not the path to a directory containing a file named config.json. Checkout your internet con…

【C++】set和map的底层结构(AVL树红黑树)

文章目录 一、前言二、AVL 树1.AVL树的概念2.AVL树节点的定义3.AVL树的插入4.AVL树的旋转5.AVL树的验证6.AVL树的删除、AVL树的性能 三、红黑树1.红黑树的概念2.红黑树的性质3.红黑树节点的定义4.红黑树结构5.红黑树的插入操作6.红黑树的验证7.红黑树与AVL树比较 四、红黑树模拟…

【藏经阁一起读】(77)__《Apache Dubbo3 云原生升级与企业最佳实践》

【藏经阁一起读】&#xff08;77&#xff09; __《Apache Dubbo3 云原生升级与企业最佳实践》 目录 一、Dubbo是什么 二、Dubbo具体提供了哪些核心能力&#xff1f; 三、构建企业级Dubbo微服务 &#xff08;一&#xff09;、创建项目模板 &#xff08;二&#xff09;、将…

OpenGL_Learn15(投光物)

1. 平行光 cube.vs******************#version 330 core layout (location 0) in vec3 aPos; layout (location 1 ) in vec3 aNormal; layout (location2) in vec2 aTexCoords;out vec3 FragPos; out vec3 Normal; out vec2 TexCoords;uniform mat4 model; uniform mat4 view…

基于猎食者算法优化概率神经网络PNN的分类预测 - 附代码

基于猎食者算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于猎食者算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于猎食者优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络…

C 语言获取文件绝对路径

示例代码 1&#xff0c;不包含根目录绝对路径&#xff1a; #include <stdlib.h> #include <stdio.h>int main(void) {char *fileName "/Dev/test.txt";char *abs_path _fullpath(NULL, fileName, 0);printf("The absolute path is: %s\n", a…