juc-3-volatile

news2024/9/22 13:36:55

目录

1 多线程下变量的不可见性及解决方案

 2 不可见性解决方案

2.1 加锁方式解决

2.2 使用volatile关键字

2.3 while(true)速度快调用本地线程内存,加延时即可访问主内存的共享变量

3 volatile不保证原子性以及解决方案

3.1 案例 volatile 验证不是原子性

3.2  讲解count++和count-- 不是原子操作

3.3 解决volatile 原子性问题

4  volatile 禁止指令重排序

4  实战(懒汉式 双重检查+volatile )

 5  场景(只适合纯赋值 )


总结

  1. 线程间的通讯有不可见性。
  2. volatile能使线程通讯可见,但是不保证原子性。(解决不了库存超卖) 
  3. volatile 禁止指令重排序

1 多线程下变量的不可见性及解决方案

在多线程并发执行下,多个线程修改共享的成员变量,会出现一个线程修改共享变量后,另一个线程,不能直接看到线程修改后的变量的最新值。

 案例 (不可见性)

个人见解流程分析:1 new volatileDemo();将成员变量flag = false;加载到内存中去。

       2 开启线程,将 主内存中 flag = false; 读取到副本中去,并作修改 flag = true; 

      3 向主内存中刷新为 flag = true; 

      4  cup 去执行 main线程 ,将 主内存中 flag =true; 读取到main线程的 副本中去。打印日志,"主线程获取flag"

public class volatileDemo implements Runnable {
    private boolean flag = false;

    @Override
    public void run() {
        // 2子线程已经将flag变成true了
        flag = true;
        System.out.println(Thread.currentThread().getName()+"flag="+flag);
    }
    public boolean getFlag(){
        return flag;
    }
    public static void main(String[] args) {
        //同一个对象,同一变量 a 锁的是同一个对象,线程安全的
        volatileDemo thread = new volatileDemo();
        // 1 子线程开始执行
        new Thread(thread).start();
        while (true){
           // 3 获取flag时已经由子线程将它变成 true了
           if(thread.getFlag()){
               System.out.println("主线程获取flag");
           }
        }
    }
}

验证不可见性(仅仅加上一个延时)

个人见解流程分析:1 new volatileDemo();将成员变量flag = false;加载到内存中去。

       2 开启子线程,由于延时一秒(放弃CPU的时间片),主内存中还是  flag = false; 并且让cpu去执行main线程。

       3  main线程 将 主内存中 flag =false; 读取到main线程的 副本中去。

       4 子线程将 flag = true; 并刷新到主内存中。

       5  由于 while(true)速度比较快,只会读 main线程的副本,不会读主内存数据 。所以不会有打             印,"主线程获取flag"

    

JMM规定

  1. 所有的共享变量都存储于主内存中,这里说的变量不包含局部变量 ,因为局部变量是私有的,因此不存在竞争问题。
  2. 每一个线程还存在自己的工作内存,线程的工作内存,保留了共享变量的副本。
  3. 线程对变量的所有操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量。
  4. 不同线程间,也不能直接访问对方工作内存中的变量,线程间的变量的值的传递需要通过主内存中转完成。
  5. 总结   1.线程读取共享变量到自己的副本中
              2.更改副本的值
              3.刷新到主内存

 2 不可见性解决方案

 如何实现多线程间访问共享变量的可见性?

  1. 加锁
  2. 使用volatile关键字
  3. while(true)速度快调用本地线程内存,加延时即可访问主内存的共享变量

2.1 加锁方式解决

讲解:某一个线程进入到synchronized代码块中,执行过程如下:

  1. 线程获取锁     
  2. 清空工作内存(本地内存)   
  3. 从主内存拷贝共享变量最新值到工作内存中称为副本。
  4. 执行代码,将修改后的副本刷新会主内存中
  5. 线程释放锁。

 

2.2 使用volatile关键字

 流程原理

 

2.3 while(true)速度快调用本地线程内存,加延时即可访问主内存的共享变量

 

volatile不保证原子性以及解决方案

3.1 案例 volatile 验证不是原子性

public class ThreadRunnable2 implements Runnable {
    private volatile int a = 100;
    @SneakyThrows @Override
    public void run() {
        while (true) {
            if (a > 1) {
                a--;
                System.out.println(Thread.currentThread().getName() + "==" + a);
                Thread.sleep(100);
            }
        }
    }
    public static void main(String[] args) {
        ThreadRunnable2 thread = new ThreadRunnable2();
        new Thread(thread).start();
        new Thread(thread).start();
    }
}

 

3.2  讲解count++和count-- 不是原子操作

 

 

3.3 解决volatile 原子性问题

加锁

 

使用原子类

AtomicBoolean可以用原子方式更新的 boolean 值。
AtomicInteger可以用原子方式更新的 int 值。
AtomicIntegerArray可以用原子方式更新其元素的 int 数组。
AtomicIntegerFieldUpdater<T>基于反射的实用工具,可以对指定类的指定 volatile int 字段进行原子更新
AtomicLong可以用原子方式更新的 long 值。
AtomicLongArray可以用原子方式更新其元素的 long 数组
AtomicLongFieldUpdater<T>基于反射的实用工具,可以对指定类的指定 volatile long 字段进行原子更新。
AtomicMarkableReference<V>AtomicMarkableReference 维护带有标记位的对象引用,可以原子方式对其进行更新。
AtomicReference<V>可以用原子方式更新的对象引用。
AtomicReferenceArray<E>可以用原子方式更新其元素的对象引用数组。
AtomicReferenceFieldUpdater<T,V>基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新
AtomicStampedReference<V>AtomicStampedReference 维护带有整数"标志"的对象引用,可以用原子方式对其进行更新

    public static void main(String[] args) {
        //无参构造初始值为0 ,有参构造可以指定初始值
        AtomicInteger atomicInteger = new AtomicInteger();
        //获取当前值
        int i = atomicInteger.get();
        // 加 1操作 并返回前一个值  
        //例如:初始值是0 执行完getAndIncrement(),返回结果是 0 ,但是当前值是 1。
        int andIncrement = atomicInteger.getAndIncrement();
        // 减1操作 并返回前一个值
        int andDecrement = atomicInteger.getAndDecrement();
        // 加 20 并返回最终结果
        //例如: 原值是5,返回结果将是25。
        int i1 = atomicInteger.addAndGet(20);
        // 设置新值 返回旧值
        int andSet = atomicInteger.getAndSet(30);
    }

4  volatile 禁止指令重排序

指令重排序:cpu为了提高我们的处理速度可能会对我们的代码指令重排序。但是在并发执行下,会有较小的概率出现和我们预期不符的结果。

案例(指令重排序)(执行了68万没有复现。就不打算执行了)

public class volatileDemo2 {

    public static int a = 0, b = 0;
    public static int i = 0, j = 0;

    public static void main(String[] args) throws InterruptedException {
        int count = 0;
        while (true) {
            count++;
            Thread one = new Thread(() -> {
                a = 1;
                i = b;
            }, "线程A");
            Thread two = new Thread(() -> {
                b = 1;
                j = a;
            }, "线程B");

            one.start();
            two.start();
            one.join();
            two.join();
            String result = "第 " + count + "次" + " i= " + i + " , j= " + j;
            System.out.println(result);
            if (i == 0 && j == 0) {
                break;
            }
        }
    }
}

以上代码打印可的情况分析

解决办法

 

实战(懒汉式 双重检查+volatile

public class volatileDemo3 {
    //静态属性 ,volatile保证可见性和禁止指令重排
    private volatile static volatileDemo3 instance = null;

    //私有化构造器
    private volatileDemo3() {
    }
    public static volatileDemo3 getInstance() {
        //第一重检查
        if (instance == null) {
            synchronized (volatileDemo3.class) {
                //第二重检查
                if (instance == null) {
                    instance = new volatileDemo3();
                }
            }
        }
        return instance;
    }
}

 

 5  场景(只适合纯赋值 )

 

  //纯赋值不会设计到原子操作
  //  flag = true;
  // 涉及到获取值和 向主内存刷新值,这都不是原子操作。 原理和count++一个道理。
   flag = !flag;
public class volatileDemo4 implements Runnable {
    private volatile boolean flag = false;
    AtomicInteger atomicInteger = new AtomicInteger();
    @SneakyThrows @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
             //纯赋值不会设计到原子操作
           //  flag = true;
            // 涉及到获取值和 向主内存刷新值,这都不是原子操作。 原理和count++一个道理。
            flag = !flag;
            atomicInteger.getAndIncrement();
        }
    }
    public boolean getFlag(){

        return flag;
    }
    public static void main(String[] args) throws InterruptedException {
        volatileDemo4 thread = new volatileDemo4();
        // 1 子线程开始执行
        Thread thread1 = new Thread(thread);
        Thread thread2 = new Thread(thread);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(thread.getFlag()+"次数"+thread.atomicInteger);
    }
}

 

 

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

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

相关文章

详细解析预处理

预处理一.总体概述1.注释去除2.宏替换二.宏定义1.数值宏常量2.字符串宏常量3.用宏定义注释符号4.用宏定义表达式&#xff08;难点&#xff09;1.第一种情况2.第二种情况5.#undef&#xff08;宏的有效范围&#xff09;1.两个问题2.#undef的使用3.一段代码的理解三.条件编译1.#if…

基于nodejs商城系统开发与设计(项目源码+论文设计+ppt答辩+视频录制)

网上购物商城系统以弥补传统购物方式的弊端。在目前的商城里&#xff0c;如果采用网上商城方式&#xff0c;用户购物时就不需要到店里面排队&#xff0c;这样不仅能实时地了解商品的特色&#xff0c;而且方便了顾客&#xff0c;同时也减轻了商城的服务压力。随着WLAN技术的普及…

计算机毕设Python+Vue新文道考研机构在线教学辅导系统(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

python的panda库读写文件

目录 1.读取excel文件 &#xff08;1&#xff09;语法 &#xff08;2&#xff09;实例 2.读取cvs文件 &#xff08;1&#xff09;语法 &#xff08;2&#xff09;实例 3.读取txt文件 &#xff08;1&#xff09;语法 &#xff08;2&#xff09;实例 4.写入文件 &…

【driver.js】基础使用

介绍 driver.js: 轻量级、无依赖性、普通的 JavaScript 引擎&#xff0c;可在整个页面上推动用户的注意力; &#x1f506;突出显示页面上的任何&#xff08;字面上的任何&#xff09;项目✋阻止用户交互&#x1f4e3; 创建功能介绍&#x1f453;为用户添加焦点转移器&#x1f6…

【Flask框架】——21 Flask上下文

上下文&#xff1a;即语境&#xff0c;语意&#xff0c;在程序中可以理解为在代码执行到某一时刻时&#xff0c;根据之前代码所做的操作以及下文即将要执行的逻辑&#xff0c;可以决定在当前时刻下可以使用到的变量&#xff0c;或者可以完成的事情。 Flask中有两种上下文&…

UDP的报文结构和注意事项

UDP的报文结构和注意事项一、传输层协议二、UDP报文结构一、传输层协议 传输层实现了端到端之间的传输&#xff0c;重点关注的是起点和终点。 核心的协议有两个&#xff1a; 二、UDP报文结构 大部分教科书给出的报文结构都是这样的&#xff1a; 其实只是为了排版方便~~ 实…

关于进程的几个问题

作者&#xff1a;~小明学编程 文章专栏&#xff1a;JavaEE 格言&#xff1a;热爱编程的&#xff0c;终将被编程所厚爱。 今天给大家分享几个关于进程的小问题 1.什么是进程&#xff1f; 2.进程是怎么管理的&#xff1f; 3.进程里面的PCB里都有啥&#xff1f; 4.进程的调度是怎…

解释器模式

文章目录解释器模式1.解释器模式的本质2.何时选用解释器模式3.优缺点4.解释器模式的结构5.实现计算器加减操作解释器模式 当想解析一个文件或者其他内容时&#xff0c;可以根据规律自己定义一种文法&#xff0c;并定义一个解释器&#xff0c;然后解析这种文法&#xff0c;以达到…

MATLAB-自定义函数拟合(fittype-高斯拟合)

在回归拟合分析时,一般情况下,MATLAB会直接提供常用的类型,用fittype创建拟合模型,至于MATLAB具体提供了哪些模型,参见帮助“List of library models for curve and surface fitting”,如果库中没有自己想要的拟合表达式形式,可以自己进行定义,具体介绍如下: 1. fitty…

嵌入式:ARM间接寻址、变址寻址与多寄存器寻址

文章目录寄存器间接寻址基址加偏址寻址&#xff08;变址寻址&#xff09;偏移地址传送数据类型块拷贝寻址&#xff08;多寄存器寻址&#xff09;块拷贝寻址示例寄存器间接寻址 寄存器间接寻址就是以寄存器中的值作为操作数的地址&#xff0c;而操作数本身存放在存储器中。例如…

尚医通 (三十七) --------- 定时任务与统计

目录一、就医提醒1. 搭建定时任务模块 service-task2. 添加就医提醒处理二、预约统计1. ECharts2. 获取医院每天平台预约数据接口3. 添加 feign 方法4. 搭建 service-statistics5. 前端展示一、就医提醒 我们通过定时任务&#xff0c;每天 8 点执行&#xff0c;提醒就诊。 1.…

[附源码]Python计算机毕业设计Django校园招聘系统设计

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Android Accessibility知识分享

工具 工欲善其事&#xff0c;必先利其器。下面我们介绍一下工具来发现我们的accessibility问题。感谢这篇文章的分享&#xff1a;https://www.kodeco.com/240-android-accessibility-tutorial-getting-started Lint&#xff1a;这个工具是google提供的&#xff0c;在android …

python--面向对象以及其三大特性(封装、继承、多态)

文章目录前言一、面向过程二、 面向对象三、面向对象的三大特性一、封装二、继承私有属性和私有方法三、多态四、高级特性单例模式前言 这一节主要学习面向对象以及面向对象的三大特性&#xff1a;封装、继承、多态&#xff1b;还有高级特性&#xff1a;类方法、静态方法、pro…

【OpenFeign】【源码+图解】【二】注册OpenFeign接口的实例

【OpenFeign】【源码图解】【一】HelloWorld及其工作原理 目录3. 注册OpenFeign接口的实例3. 注册OpenFeign接口的实例 从HelloWorld中我们看到需要显示加入**EnableFeignClients注解才能开启openFeign的功能&#xff0c;因此它就成为我们分析openFeign**的入口&#xff0c;先…

Chrony时间同步服务

目录 一、时间同步 1.概念 2.时间同步在运维工作中的作用 3.时间同步完成方法 &#xff08;1&#xff09;NTP时间服务&#xff08;centos 6 &#xff09; &#xff08;2&#xff09;Chrony时间服务 二、Chrony时间服务 1.Chrony介绍 2.Chrony的优点 三、Chrony安装 …

逻辑回归(Logistic Regression)原理及过程

目录 一&#xff1a;逻辑回归简介 二&#xff1a;逻辑回归原理 三&#xff1a;逻辑回归 损失函数 四&#xff1a;逻辑回归 梯度下降算法 五&#xff1a;逻辑回归 过程 一&#xff1a;逻辑回归简介 Logistic模型是1938年Verhulst-Pearl在修正非密度方程时提出来的&#xf…

算法刷题打卡第49天:排序数组---计数排序

排序数组 难度&#xff1a;中等 给你一个整数数组 nums&#xff0c;请你将该数组升序排列。 示例 1&#xff1a; 输入&#xff1a;nums [5,2,3,1] 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;nums [5,1,1,2,0,0] 输出&#xff1a;[0,0,1,1,2,5]计数排…

我与世界杯的故事——达利奇:铜牌闪耀着金光

目录 克罗地亚球队的历史 奇迹出现 心得总结 克罗地亚球队的历史 克罗地亚球队拥有悠久的历史&#xff1a; 1998年首次亮相法国世界杯&#xff0c;克罗地亚就以季军的战绩惊艳众人。 2018年的俄罗斯世界杯&#xff0c;虽然格子军团在决赛中不敌强大的法国&#xff0c;遗憾地…