线程进阶(以解决线程安全问题为主)、volatile的底层实现

news2024/11/22 13:27:36

线程:以解决线程安全问题为主

进程:运行时程序,操作系统分配内存资源的最小单位。

线程 :进程内部最小执行单元。

多线程的优点:提高程序响应速度,可以多个线程各自完成自己的工作,提高设备利用率。

缺点:在多个线程同时访问共享数据,可能会出现资源共享问题。

并发执行:在一个时间段内对多个线程依次执行

并行执行:是真正意义上同时执行,两个线程在同一时间节点上一起执行

并发编程的核心问题:

1,不可见性:一个线程对共享变量修改,另一个线程不能立刻看到,称不可见性。(缓存不能及时刷新导致)
public class Demo {
    public static void main(String[] args) {
        RunTask runTask=new RunTask();//创建任务对象
        Thread thread=new Thread(runTask);//创建线程
        thread.start();//启动线程

        while(true){
            if(!runTask.isFlag()) {
                System.out.println("main:"+runTask.isFlag());
                break;
            }
        }
    }
}
public class RunTask implements Runnable{
   private boolean flag=false;
    @Override
    public void run() {
        flag=true;//设置此时flag为true
        System.out.println(flag);
    }

    public  boolean isFlag() {
        return flag;
    }

    public  void setFlag(boolean flag) {
        this.flag = flag;
    }
}

 

先启动了一个线程任务,设置的flag为true。但是在main线程取出flag为false。发现读取的数据不一致,说明了线程的不可见性。我们该如何避免这种,在一个线程修改后其他线程可以立刻看见修改后的数据呢?----------添加关键字volatile


public class RunTask implements Runnable{
   private volatile boolean flag=false;
    @Override
    public void run() {
        flag=true;//设置此时flag为true
        System.out.println(flag);
    }

    public  boolean isFlag() {
        return flag;
    }

    public  void setFlag(boolean flag) {
        this.flag = flag;
    }
}

为要修改的变量添加修饰volatile发现可以使改变的变量立即被看见。

 volatile修饰的变量在一个线程中被修改后,对其他线程立即可见。并且禁止指令重排。

volatile的底层实现

 该关键字可以确保对一个变量的更新对其他线程马上可见。当一个变量被声明为 volatile 时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。当其他线程读取该共享变量时,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。volatile 的内存语义和synchronized有相似之处,具体来说就是,当线程写入了 volatile变量值时就等价于线程退出synchronized同步块(把写入工作内存的变量值同步到主内存),读取 volatile 变量值时就相当于进入同步块(先清空本地内存变量值,再从主内存获取最新值)。

 volatile 虽然提供了可见性保证,但并不保证操作的原子性。


那么一般在什么时候才使用 volatile关键字呢?
①写入变量值不依赖变量的当前值时。因为如果依赖当前值,将是获取一计算一写入三步操作,这三步操作不是原子性的,而 volatile 不保证原子性。
②读写变量值时没有加锁。因为加锁本身已经保证了内存可见性,这时候不需要把变量声明为volatile的。

2,乱序性:指令在执行过程中改变顺序,可能会影响程序运行结果

为了优化性能,有时候会改变程序中语句的先后顺序。

public class Test {
    static int a = 0, b = 0, x = 0, y = 0;

    public static void main(String[] args) {
        while (true) {
            x=0;
            y=0;
            a=0;
            b=0;
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    a = 1;
                    x = b;
                }
            });
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 1;
                    y = a;
                }
            });
            t1.start();
            t2.start();
            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (x == 0 && y == 0) {
                System.err.println(x + "---" + y);
                break;
            }else{
                System.out.println(x+" "+y);
            }
        }
    }
}

每次执行每个线程的count值都不同。由于三个线程是并发执行,所以不确定他们的执行顺序,这就是并发的乱序问题。

解决办法:

1,上锁

    public void increment() {
        synchronized (obj) {
            count++; // 这是一个原子操作,不可中断
            System.out.println("Thread " + Thread.currentThread().getId() + " incremented count to " + count);
        }
    }

2, volatile

3,非原子性:线程切换带来的非原子性问题

A线程执行时,被切换到B线程。

使用Javap-c命令查看汇编代码,如下所示。
        public void inc();
        Code:
                0:aload0
                1:dup
                2:getfield
                5:Iconst 1
                6:ladd
                7:putfield10:return
由此可见,简单的++value 由2、5、6、7四步组成,其中第2步是获取当前 value的值并放入栈顶,第5步把常量1放入栈顶,第6步把当前栈顶中两个值相加并把结果放入栈顶,第7步则把栈顶的结果赋给 value变量。因此,Java中简单的一句+value 被转换为汇编后就不具有原子性了。 

解决方案:

1,使用synchronized关键字

2,CAS机制:不加锁的机制

public class Demo {
    private volatile  int count = 0;
    public static void main(String[] args) {
        Demo demo = new Demo();
        // 创建并启动三个线程,它们都会访问demo对象的increment()方法
        Thread t1 = new Thread(() -> {
            demo.increment();
        });
        Thread t2 = new Thread(() -> {
            demo.increment();
        });
        Thread t3 = new Thread(() -> {
            demo.increment();
        });

        t1.start();
        t2.start();
        t3.start();

        try {
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出最终的count值,由于并发中的乱序问题,这个值可能会与预期不符
        System.out.println("Final count: " + demo.count);
    }

    public void increment() {
            System.out.println("Thread " + Thread.currentThread().getId() + " incremented count to " + ++count);
    }
}

 

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

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

相关文章

深入理解MyBatis缓存机制:一级缓存与二级缓存详解

深入理解MyBatis缓存机制:一级缓存与二级缓存详解 MyBatis作为一款优秀的持久层框架,其缓存机制是其核心功能之一。在MyBatis中,我们通常会遇到一级缓存和二级缓存,它们分别在不同的场景中发挥着重要作用。本文将深入探讨一级缓存…

鸿蒙不兼容安卓!正式迈入“完全自主研发”阶段,余承东最新发声!

2019年8月9日,华为鸿蒙“备胎”的一夜转正,四年多后的今天(1月18日),华为鸿蒙再度令各界惊喜,因为在这一天,华为正式推出了完全自主研发的鸿蒙版本:HarmonyOS NEXT鸿蒙星河版&#x…

IMX6LL|时钟控制

一.时钟控制模块 4个层次配置芯片时钟 晶振时钟PLL与PFD时钟PLL选择时钟根时钟/外设时钟 1.1晶振时钟 系统时钟来源 RTC时钟源:32.768KHz,连接RTC模块,进行时间计算。系统时钟:24MHz,芯片主晶振 1.2PLL和PFD倍频时钟…

MySQL进阶45讲【2】日志系统:一条SQL更新语句是如何执行的?

1 前言 上篇文章我们系统了解了一个查询语句的执行流程,并介绍了执行过程中涉及的处理模块。相信大家还记得,一条查询语句的执行过程一般是经过连接器、分析器、优化器、执行器等功能模块,最后到达存储引擎。 那么,一条更新语句…

【Nuxt3】目录中components文件夹的用法

简言 在Nuxt3中,components文件夹和vue文件夹用处一样,都是放置vue公共组件的地方。只不过由于Nuxt3中components文件内的组件自动导入机制,用法些许不同。 components components/ 目录是你放置所有 Vue 组件的地方。 Nuxt 会自动导入该目…

刷题 ------ 双指针

文章目录 1.验证回文串 ||2.计数二进制字串3. 字符的最短距离4.按奇偶排序数组5.仅仅反转字母6. 奇偶排序数组 ||7.长按键入8. 递减字符匹配9.有序数组的平方10.复写零11.删除回文子序列12.检查单词是否为剧中其他单词的前缀13.交替合并的字符串14.反转单词前缀15.找出数组中的…

阿赵UE学习笔记——11、地形系统

阿赵UE学习笔记目录 大家好,我是阿赵。   继续学习虚幻引擎的用法,这次来学习一下虚幻引擎的地形系统的用法。 一、创建地形 在选项模式里面,选择地形: 进入到地形界面之后,需要先创建一个地形: 留意看…

npm依赖库备份

常用命令 设置默认使用本地缓存安装Nodejs时会自动安装npm,但是局路径是C:\Users\Caffrey\AppData\Roaming\npm默认的缓存路径是C:\Users\Caffrey\AppData\Roaming\npm-cache;查看npm的prefix和cache路径配置信息设置路径 设置默认使用本地缓存 npm con…

李沐《动手学深度学习》线性神经网络 softmax回归

系列文章 李沐《动手学深度学习》预备知识 张量操作及数据处理 李沐《动手学深度学习》预备知识 线性代数及微积分 李沐《动手学深度学习》线性神经网络 线性回归 目录 系列文章一、softmax回归(一)问题背景(二)网络架构&#xf…

路飞项目--02

补充:axios封装 # 普通使用:安装 ,导入使用 const filmListreactive({result:[]}) axios.get().then() async function load(){let responseawait axios.get()filmList.resultresponse.data.results } # 封装示例:请求发出去之前…

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)

实验7:虚拟存储器以及虚拟变换 一、实验目的 1:加深对虚拟存储器基本概念、基本组织结构以及基本工作原理的理解。 2:掌握页式、段式,段页式存储的原理以及地址变换的方法。 3:理解LRU与随机替换的基本思想。 二、…

easy Exsel导出

目录 一、首先引入依赖 二、然后封装一个VO 三、Controller层 四、Service实现类 引用样式 自适应列宽 自适应行高 五、测试 postman ​编辑 浏览器 异常 分配到这个任务了,写个小demo记录下,具体可参考EasyExcel官方文档 我用的是web上传…

redis数据安全(五)事务

一、概念: 1、介绍:Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命…

css实现动态水波纹效果

效果如下: 外层容器 (shop_wrap): 设置外边距 (padding) 提供一些间距和边距 圆形容器 (TheCircle): 使用相对定位 (position: relative),宽度和高度均为 180px,形成一个圆形按钮圆角半径 (border-radius) 设置为 50%&…

Linux编译器--gcc和g++使用

gcc和g使用 一、gcc/g的作用1.1 预处理1.2 编译1.3 汇编1.4 链接 二、静态库和动态库三、make/Makefile3.1 make/Makefile3.2 依赖关系和依赖方法3.3 多文件编译3.4 make原理3.5 项目清理 四、linux下的第一个小程序-进度条4.1 行缓冲区的概念4.2 \r和\n4.3 进度条代码 一、gcc…

rt-thread修改全局中断屏蔽函数,解决内核频繁关闭中断影响精密计时问题

带rtt-nano实时操作系统的小板子需要读取单总线设备,使用软件延时吧,总是由于时隙不精确,通信不稳定。按说不稳定情况也不频繁,但考虑到未来需要对上百、上千米外的单总线设备通信,开发的时候偷个懒,到应用…

Jmeter后置处理器——JSON提取器

目录 1、简介 2、使用步骤 1)添加线程组 2)添加http请求 3) 添加JSON提取器 1、简介 JSON是一种简单的数据交换格式,允许互联网应用程序快速传输数据。JSON提取器可以从JSON格式响应数据中提取数据、简化从JSON原始数据中提取特定…

《Unix环境高级编程》第三版源代码编译报错汇总(WSL)

文章目录 Error: unable to disambiguate: -dylib (did you mean --dylib ?)undefined reference to majorerror: ‘FILE’ has no member named ‘__pad’; did you mean ‘__pad5’?error: ‘FILE’ has no member named ‘_flag’; did you mean ‘_flags’?error: ‘FIL…

AAAI 2024 | TEx-Face,5秒内按需生成照片级3D人脸

本文介绍一篇来自浙江大学ReLER实验室的工作,"Controllable 3D Face Generation with Conditional Style Code Diffusion",目前该文已被AAAI 2024录用。 论文题目: Controllable 3D Face Generation with Conditional Style Code D…

(C语言)冒泡排序

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>//实现buble_sort函数&#xff1b; void buble_sort(int arr[], int sz) {//初始化变量值&#xff1b;int i 0;//嵌套循环冒泡排序&#xff1b;//外层循环&…