JUC之可见性和有序性

news2024/11/16 7:42:05

目录

java内存模型

可见性

现象出现 

现象解释 

解决方法

有序性

诡异的结果

解决方法

Happens-before规则


java内存模型

Java内存模型(Java Memory Model,简称JMM)定义了Java程序中各种变量、对象的访问方式和内存关系。JMM规定了线程之间的可见性、原子性、顺序性等问题,确保多线程并发访问时的代码正确性。

JMM中的主要概念包括:

  1. 主内存与工作内存

    主内存是Java的内存模型中的高速缓存,是共享变量的存储区域,所有的线程都可以访问它。

    工作内存是每个线程的私有内存,其中保存了线程执行时所需的变量副本。线程对共享变量的读写操作都在工作内存中进行,线程之间不能直接读写彼此的工作内存。

  2. 原子性

    在JMM中,原子性是指一个操作是不可分割的整体,要么全部执行成功,要么全部执行失败。JMM保证单个变量的读取和赋值操作具有原子性,如果希望在多个变量上实现原子操作,需要加锁或者使用原子类。

  3. 可见性

    可见性是指一个线程修改的变量对其他线程是可见的。在JMM中,并不保证一个线程修改变量后,另一个线程能够立即看到这个变化,这是因为每个线程都有自己的工作内存,线程之间不能直接读写彼此的工作内存。如果需要确保可见性,可以使用volatile关键字或者加锁同步。

  4. 有序性

    有序性是指程序执行的顺序与代码编写的顺序是一致的。在JMM中,并不保证程序的执行顺序与代码编写的顺序完全一致,但是会通过各种手段尽量保证程序执行的结果符合预期。

JVM通过约束JMM,来规范多线程并发执行的行为,从而保证Java程序的正确性。虽然JMM是一个模型,但是JVM在实现时会对其进行优化,所以需要开发人员注意编写线程安全、正确、高效的程序。

JMM 体现在以下几个方面

  • 原子性 - 保证指令不会受到线程上下文切换的影响
  • 可见性 - 保证指令不会受 cpu 缓存的影响
  • 有序性 - 保证指令不会受 cpu 指令并行优化的影响

可见性

现象出现 

先来看一个现象,main 线程对 run 变量的修改对于 t 线程不可见,导致了 t 线程无法停止:  

public class ThreadText  {
    static boolean run = true;

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

        ThreadText t = new ThreadText();

        new Thread(() -> {
            System.out.println("线程启动了");
            while (run) {
//                 System.out.println("线程进行中");
            }
            System.out.println("线程即将结束了");
        }).start();

        Thread.sleep(100);

        run = false;
        System.out.println("线程状态发生改变");
    }
    
}

 现象1:如果你直接运行上面代码,新创建的线程并不会停止。

 现象2:如果你打开我注释的代码的话,新创建的线程会停止

现象解释 

为什么呢?分析一下:

1. 初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存。

2. 因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中, 减少对主存中 run 的访问,提高效率。

3. 1 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量 的值,结果永远是旧值

解决方法

volatile(易变关键字) 它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取 它的值,线程操作 volatile 变量都是直接操作主存

可见性 vs 原子性

前面例子体现的实际就是可见性,它保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一个线程可 见, 不能保证原子性,仅用在一个写线程,多个读线程的情况: 上例从字节码理解是这样的:

getstatic run // 线程 t 获取 run true 
getstatic run // 线程 t 获取 run true 
getstatic run // 线程 t 获取 run true 
getstatic run // 线程 t 获取 run true 
putstatic run // 线程 main 修改 run 为 false, 仅此一次 
getstatic run // 线程 t 获取 run false 

两个线程一个 i++ 一个 i-- ,只能保证看到最新值,不能解决指令交错

// 假设i的初始值为0 
getstatic i // 线程2-获取静态变量i的值 线程内i=0 
 
getstatic i // 线程1-获取静态变量i的值 线程内i=0 
iconst_1 // 线程1-准备常量1 
iadd // 线程1-自增 线程内i=1 
putstatic i // 线程1-将修改后的值存入静态变量i 静态变量i=1 
 
iconst_1 // 线程2-准备常量1 
isub // 线程2-自减 线程内i=-1 
putstatic i // 线程2-将修改后的值存入静态变量i 静态变量i=-1 

注意 synchronized 语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是

synchronized 是属于重量级操作,性能相对更低 如果在前面示例的死循环中加入 System.out.println() 会发现即使不加 volatile 修饰符,线程 t 也能正确看到 对 run 变量的修改了,想一想为什么?

System.out.println导致了线程同步

public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }


虽然没有volatile保证多线程下共享数据的可见性, 但是JMM(Java内存模型)里面的happens-before原则里面照样有其它保证数据一致性的约束存在.

有序性

JVM 会在不影响正确性的前提下,可以调整语句的执行顺序,思考下面一段代码

static int i;
static int j;
// 在某个线程内执行如下赋值操作
i = ...; 
j = ...; 

 可以看到,至于是先执行 i 还是 先执行 j ,对最终的结果不会产生影响。所以,上面代码真正执行时,既可以是

i = ...; 
j = ...;

也可以是

j = ...;
i = ...; 

这种特性称之为『指令重排』,多线程下『指令重排』会影响正确性。为什么要有重排指令这项优化呢?从 CPU执行指令的原理来理解一下吧

诡异的结果

    int num = 0;
    boolean ready = false;
    // 线程1 执行此方法
    public void actor1(I_Result r) {
        if (ready) {
            r.r1 = num + num;
        } else {
            r.r1 = 1;
        }
    }
    // 线程2 执行此方法
    public void actor2(I_Result r) {
        num = 2;
        ready = true;
    }

I_Result 是一个对象,有一个属性 r1 用来保存结果,问,可能的结果有几种?

情况1:线程1 先执行,这时 ready = false,所以进入 else 分支结果为 1

情况2:线程2 先执行 num = 2,但没来得及执行 ready = true,线程1 执行,还是进入 else 分支,结果为1

情况3:线程2 执行到 ready = true,线程1 执行,这回进入 if 分支,结果为 4(因为 num 已经执行过了)

结果还有可能是 0

这种情况下是:线程2 执行 ready = true,切换到线程1,进入 if 分支,相加为 0,再切回线程2 执行 num = 2

这种现象叫做指令重排,是 JIT 编译器在运行时的一些优化,这个现象需要通过大量测试才能复现。

解决方法

volatile 修饰的变量,可以禁用指令重排。

当一个变量被声明为volatile时,编译器和运行时都会受到限制,不能对这个变量进行指令重排。具体地说,对于volatile变量的读操作和写操作都会通过内存屏障来保证它们的顺序性和可见性。

内存屏障是一组CPU指令,可以防止处理器对内存访问进行重排序,并保证某些指令的执行顺序。对于读操作,内存屏障会使CPU在执行读指令前,强制将之前的所有修改数据的指令刷回主存;对于写操作,内存屏障会使CPU在执行写指令后,强制将数据写入主存。

Happens-before原则是Java内存模型中的一个概念,它定义了在并发情况下,对共享变量的写操作和读操作之间的可见性关系,包括线程启动、线程终止、同步块、volatile变量等多种场景,确保了多个线程之间的操作顺序。

Happens-before规则

happens-before 规定了对共享变量的写操作对其它线程的读操作可见,它是可见性与有序性的一套规则总结,抛开happens-before 规则,JMM 并不能保证一个线程对共享变量的写,对于其它线程对该共享变量的读可见

具体地,如果操作A happens-before操作B,那么我们可以保证看到操作A的线程对共享变量的修改对操作B的线程是可见的。换句话说,happens-before原则规定了不同线程之间操作执行顺序的一些规范,可以避免一些因并发带来的问题。

以下是几个重要的Happens-before规则:

  1. 程序的顺序性规则:一个线程内的每个操作按程序代码顺序执行

  2. volatile变量规则:对于一个volatile变量的写操作先于后面对该变量的读操作

  3. 传递性:如果操作A happens-before操作B,操作B happens-before操作C,则操作A happens-before操作C

  4. synchronized规则:对于同一个锁,锁的解锁操作happens-before后续的对锁的加锁操作

  5. 线程启动规则:一个线程的start()方法happens-before该线程的任何操作

  6. 线程终止规则:一个线程的所有操作都happens-before其他线程检测到该线程已经终止

  7. 对象的构造函数规则:一个对象的构造函数执行完成(happens-before)它的finalize()方法

Happens-before原则是Java实现多线程操作时的重要基础,在理解和分析多线程程序时,需要遵守和应用这些规则,避免出现线程安全问题。

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

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

相关文章

小程序-uniapp:uni-app-base 项目基础配置及使用,开箱可用

目前(20230605)uni-app最新版本(3.8.4.20230531) 一、官网文档 uni-app官网 二、创建项目 项目目标:vue3tsvitevscode 创建以 typescript 开发的工程(如命令行创建失败,请直接访问 gitee 下…

consul入门案例及配置热更新的实现及Feign的使用

Consul的简单入门 当Producer启动时,会向Consul发送一个post请求,告诉Consul自己的ip和Port;Consul接收到producer的注册后,每个10S(默认),会向producer发送一个健康检查的请求,检验Producer是否健康当Consumer发送GET方式请求/api/address到Producer时,会先从Consul中拿到一个…

Linux常用命令——gdb命令

在线Linux命令查询工具 gdb 功能强大的程序调试器 补充说明 gdb命令包含在GNU的gcc开发套件中,是功能强大的程序调试器。GDB中的命令固然很多,但我们只需掌握其中十个左右的命令,就大致可以完成日常的基本的程序调试工作。 语法 gdb(选…

DeepFace:人脸识别库 DeepFace 简单认知

写在前面 工作中遇到,简单整理博文内容为 deepface 的简单介绍理解不足小伙伴帮忙指正 对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它的路都是不完整的,是…

HarmonyOS学习路之开发篇—Java UI框架(使用工具自动生成JS FA调用PA代码)

JS FA(Feature Ability)调用PA (Particle Ability)是使用基于JS扩展的类Web开发范式的方舟开发框架所提供的一种跨语言能力调用的机制,用于建立JS能力与Java能力之间传递方法调用、处理数据返回以及订阅事件上报的通道…

代码审计——目录遍历详解

为方便您的阅读,可点击下方蓝色字体,进行跳转↓↓↓ 01 漏洞描述02 审计要点03 漏洞特征04 漏洞案例05 修复方案 01 漏洞描述 目录遍历,即利用路径回溯符“../”跳出程序本身的限制目录实现下载任意文件。 例如Web应用源码目录、Web应用配置…

FastDeploy部署参考

一、FastDeploy的gitee地址 https://gitee.com/leiqing1/FastDeploy/blob/release/0.3.0/docs/cn/faq/use_sdk_on_windows.md#21-%E4%B8%8B%E8%BD%BD%E9%A2%84%E7%BC%96%E8%AF%91%E5%BA%93%E6%88%96%E8%80%85%E4%BB%8E%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E6%9C%80%E6%96%B0%…

When viruses are good for you 病毒,有时对人体是有益的 | 经济学人20230506版社论双语精翻

本篇来自《经济学人》(The Economist)2023年5月6日社论(Leaders)精选:《病毒,有时对人体是有益的》(When viruses are good for you)。 Bacteriophages 噬菌体 When viruses are goo…

onlyoffice变量提取,处理各种办公文档

onlyoffice变量提取,处理各种办公文档 使用 ONLYOFFICE Docs 在 HumHub 中处理各种办公文档。 带有 HumHub 连接器的 ONLYOFFICE Docs 企业版允许您在灵活的开源社交网络工具包 HumHub 中实时协作查看和编辑办公文档(Word、Excel 或 PowerPoint)。它包括 ONLYOFFICE Docs&#…

代码审计——SQL注入详解

为方便您的阅读,可点击下方蓝色字体,进行跳转↓↓↓ 01 漏洞描述02 审计要点03 漏洞特征04 漏洞案例05 修复方案 01 漏洞描述 当应用程序将用户输入的内容,拼接到SQL语句中,一起提交给数据库执行时,就会产生SQL注入威…

代码随想录训练营Day60|84. 柱状图中最大的矩形

84. 柱状图中最大的矩形 class Solution {public int largestRectangleArea(int[] heights) {int res0;// 数组扩容,在头和尾各加入一个元素int [] newHeights new int[heights.length 2];newHeights[0] 0;newHeights[newHeights.length - 1] 0;for (int index…

Docker desktop 怎么切换docker源

点击setting,点击docker Engine 进行编辑 {"registry-mirrors":["https://registry.docker-cn.com","http://hub-mirror.c.163.com","https://4jup2u41.mirror.aliyuncs.com","https://docker.mirrors.ustc.edu.cn&q…

校园预付费管理系统与水电计量设备仪表的实际应用 安科瑞 许敏

摘要:论文设计了适用于学生公寓的自助式预付费控电控水管理系统,采用多种智能功能,可以监测和显示漏电现象,通过短路、跳线、零线接地等方式防范和记录用户的偷电行为,通过报警和拉闸防止事故的发生。该系统采用先进的…

霍夫变换原理

文章目录 霍夫变换原理当点都在y轴上时,用ykxb形式是无法求出参数空间中的交点,也就是累计都一样。所以就用极坐标来表示参数空间。公式求证过程 霍夫变换原理 当点都在y轴上时,用ykxb形式是无法求出参数空间中的交点,也就是累计都…

linux服务器安装nodejs

注意: 本文针对的是有linux操作基础, 会使用vim的基本操作的同学。故有些很基础的东西没有赘述,如果是纯小白的同学,看起来可能会感觉缺失一些东西。 1.nodejs官网下载自己需要的版本的node node版本选择下载地址 我使用的是14.…

图像边缘检测原理

文章目录 图像边缘检测原理1:2:3:基本边缘检测算子 图像边缘检测原理 1: 图像的边缘指的是图像中像素灰度值突然发生变化的区域,如果将图像的每一行像素和每一列像素都描述成一个关于灰度值的函数,那么图像的边缘对应在灰度值函数中是函数值突然变大的…

Autoware 安装(踩坑指南)

Autoware 安装(踩坑指南) 【Autoware】2小时安装Autoware1.13(保姆级教程) Autoware入门学习(二)——Ubuntu18.04下的源码安装和配置 上面的两篇博客安装都异常顺利,甚至没有一点报错&#xff0…

AMEYA360代理线:艾睿红外热成像仪数据机房监测应用

数据信息时代,数据机房是企业重要的区域。近日某购物平台发生的数据机房宕机事故,引发关注。机房设备温度异常,使得系统崩溃,经济损失超亿元。红外热成像仪作为一种非接触式、精准度高、可视化的温度测量工具,为数据机…

【MyBatis 神级框架】从入门到进阶

🎉🎉🎉点进来你就是我的人了博主主页:🙈🙈🙈戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔🤺🤺🤺 目录 1. 什么是 MyBatis 1.1 为什么要学MaBatis&am…

TikTok正测试AI聊天机器人Tako

该功能可以“从根本上改变应用程序中的搜索和导航” 原文链接:TikTok tests AI chatbot called Tako – The Verge TikTok正在测试一个名为Tako的AI聊天机器人,根据与The Verge共享的功能截图,它可以根据人们的问题推荐视频。 如果TikTok最…