并发编程 - 并发可见性,原子性,有序性 与 JMM内存模型

1. 并发三大特性

并发编程Bug的源头: 原子性 可见性 有序性 问题

1.1 原子性

一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行。 在 Java
中,对基本数据类型的变量的读取和赋值操作是原子性操作(64位处理器)。 不采取任何的原子性保障措施的自增操作并不是原子性的,比如i++操作。
原子性案例分析
下面例子模拟多线程累加操作
public class AtomicTest {private static volatile int counter = 0;public static void main(String[] args) {for (int i = 0; i < 10; i++) {Thread thread = new Thread(() -> {for (int j = 0; j < 10000; j++) {//synchronized (AtomicTest.class) {counter++;// }}});thread.start();}try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}//思考counter=?System.out.println(counter);}}
执行结果不确定, 与预期结果不符合,存在线程安全问题
如何保证原子性?
        1.通过 synchronized 关键字保证原子性
        2.通过 Lock锁保证原子性
        3.通过 CAS保证原子性
思考:在 32 位的机器上对 long 型变量进行加减操作是否存在并发隐患?
https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.7

1.2 可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到 修改的值。
可见性案例分析
下面是模拟两个线程对共享变量操作的例子,用来分析线程间的可见性问题
@Slf4j
public  class VisibilityTest {// volatile   -> lock addl $0x0,(%rsp)private  boolean flag = true;// private volatile boolean flag = true;//private volatile int count;public  synchronized void refresh() {// 希望结束数据加载工作flag = false;System.out.println(Thread.currentThread().getName() + "修改flag:"+flag);}public void load() {System.out.println(Thread.currentThread().getName() + "开始执行.....");while (flag) {//TODO  业务逻辑:加载数据//shortWait(10000);//synchronized可以保证可见性//System.out.println("正在加载数据......");// count++;//添加一个内存屏障   可以保证可见性//UnsafeFactory.getUnsafe().storeFence();
//            try {
//                Thread.sleep(0);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }//Thread.yield(); //让出cpu使用权}System.out.println(Thread.currentThread().getName() + "数据加载完成,跳出循环");}public static void main(String[] args) throws InterruptedException {VisibilityTest test = new VisibilityTest();// 线程threadA模拟数据加载场景Thread threadA = new Thread(() -> test.load(), "threadA");threadA.start();// 让threadA先执行一会儿后再启动线程BThread.sleep(1000);// 线程threadB通过修改flag控制threadA的执行时间,数据加载可以结束了Thread threadB = new Thread(() -> test.refresh(), "threadB");threadB.start();}

当flag没有volatile修饰时,不可见,执行结果线程A跳不出循环

运行结果:threadA没有跳出循环,也就是说threadB对共享变量flag的更新操作对threadA不可见, 存在可见性问题。

思考:上面例子中为什么多线程对共享变量的操作存在可见性问题?

当flag有volatile修饰时,具有可见性,执行结果线程A可以跳循环

当flag没有volatile修饰时,但是在load()方法内的while()中输出打印语句如:System.out.println("正在加载数据......")后,,执行结果线程A还是可以跳循环,原因是println()方法内有synchronized (this),具有可见性。

当flag没有volatile修饰时,但是在load()方法内的while()中加上内存屏障,执行结果线程A也是可以跳循环,具有可见性。
public class UnsafeFactory {/*** 获取 Unsafe 对象* @return*/public static Unsafe getUnsafe() {try {Field field = Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);return (Unsafe) field.get(null);} catch (Exception e) {e.printStackTrace();}return null;}/*** 获取字段的内存偏移量* @param unsafe* @param clazz* @param fieldName* @return*/public static long getFieldOffset(Unsafe unsafe, Class clazz, String fieldName) {try {return unsafe.objectFieldOffset(clazz.getDeclaredField(fieldName));} catch (NoSuchFieldException e) {throw new Error(e);}}}

当flag没有volatile修饰时,但是在load()方法内的while()中线程睡眠的方法:如  Thread.sleep(0);后,执行结果线程A也是可以跳循环,sleep(0)方法内部调用了内存屏障,具有可见性!

        当sleep中的时间值为0时,相当于调用了Thread.yield(); 让出cpu使用权

如何保证可见性

        1. 通过 volatile 关键字保证可见性

        2. 通过 内存屏障保证可见性

        3. 通过 synchronized 关键字保证可见性

        4. 通过 Lock锁保证可见性

1.3 有序性

        即程序执行的顺序按照代码的先后顺序执行。 为了提升性能,编译器和处理器常常会对指令做重排 序,所以存在有序性问题。
有序性案例分析
思考:下面的Java程序中x和y的最终结果是什么?
public class ReOrderTest {private static  int x = 0, y = 0;private  static  int a = 0, b = 0;public static void main(String[] args) throws InterruptedException {int i=0;while (true) {i++;x = 0;y = 0;a = 0;b = 0;/***  x,y的值是多少:  0,1 1,0  1,1  0,0*/Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {//用于调整两个线程的执行顺序shortWait(20000);a = 1; //volatile 写// 内存屏障StoreLoad   lock; addl $0,0(%%rsp)UnsafeFactory.getUnsafe().storeFence();x = b; //volatile 读}});Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {b = 1;y = a;}});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println("第" + i + "次(" + x + "," + y + ")");if (x==0&&y==0){break;}}}public static void shortWait(long interval){long start = System.nanoTime();long end;do{end = System.nanoTime();}while(start + interval >= end);}}
执行结果:x,y出现了0,0的结果,程序终止。出现这种结果有可能是重排序导致的
如何保证有序性
        1.通过 volatile 关键字保证有序性
        2.通过 内存屏障保证有序性
        3.通过 synchronized关键字保证有序性
        4.通过Lock锁保证有序性

2. Java内存模型详解

在并发编程中,需要处理的两个关键问题:

        1) 多线程之间如何通信(线程之间以何种机制来交换数据)。
        2)多线程之间如何同步 (控制不同线程间操作发生的相对顺序)。
线程之间常用的通信机制有两种:共享内存和消息传递,Java采用的是共享内存模型。

2.1 Java内存模型的抽象结构

        Java线程之间的通信由Java内存模型( Java Memory Model,简称JMM )控制,JMM决定一个 线程对共享变量的写入何时对另一个线程可见。
        从抽象的角度来看, JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内 存中,每个线程都有一个私有的本地内存,本地内存中存储了共享变量的副本。 本地内存是JMM的一 个抽象概念,并不真实存在,它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
        根据JMM的规定, 线程对共享变量的所有操作都必须在自己的本地内存中进行,不能直接从主内 存中读取
从上图看,线程A和线程B之间要通信的话,必须经历以下两个步骤:
         1)线程A把本地内存A中更新过的共享变量刷新到主内存中
        2)线程B到主内存中去读取线程A之前已更新过的共享变量
所以,线程A无法直接访问线程B的工作内存,线程间通信必须经过主内存。 JMM通过控制主内存与每 个线程的本地内存之间的交互,来为Java程序提供内存可见性的保证。

主内存与工作内存交互协议

关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工
作内存同步到主内存之间的实现细节, Java内存模型定义了以下八种 原子操作 来完成
lock(锁定): 作用于 主内存的变量 ,把一个变量标识为一条线程独占状态。
unlock(解锁): 作用于 主内存变量, 把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁 定。
read(读取): 作用于 主内存变量, 把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
load(载入): 作用于 工作内存的变量 ,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use(使用): 作用于 工作内存的变量 ,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要 使用变量的值的字节码指令时将会执行这个操作。
assign(赋值): 作用于 工作内存的变量 ,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机 遇到一个给变量赋值的字节码指令时执行这个操作。
store(存储): 作用于 工作内存的变量, 把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
write(写入): 作用于 主内存的变量 它把store操作从工作内存中得到的变量的值放入主内存的变量中。

Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:
        如果要把一个变量从主内存中复制到工作内存,就需要按顺序地执行read和load操作, 如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。但 Java内存模型只要求上述操作必须按顺序执行,而 没有保证必须是连续执行。
        不允许read和load、store和write操作之一单独出现。
        不允许一个线程丢弃它的最近assign的操作,即 变量在工作内存中改变了之后必须同步到主内存中。
        不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
        一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
        一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行 lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现
        如果对一个变量执行lock操作,将会清空工作内存中此变量的值 ,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
        如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
        对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)

可见性案例深入分析

        重点:结合可见性案例理解主内存和工作内存的交互过程

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

常用字符串函数拓展

文章目录 字符串拓展函数strncpystrncatstrncmpstrstrstrtokstrerrormemcpymemmovememcmpmemset 库函数模拟实现memmoveqsort 我们在学习C语言时已经学习了一些常见的字符串函数&#xff0c;但这还不能满足我们的需求&#xff0c;为此我们拓展了几个常用的字符串函数。 字符串拓…

Maven项目转为SpringBoot项目

Maven项目转为SpringBoot项目 前言创建一个maven项目前的软件的一些通用设置Maven仓库的设置其他的设置字符编码编译器注解支持 创建的Maven项目修改为Spring Boot项目修改pom.xml文件修改启动类-Main新建WAR包所需的类 添加核心配置文件 测试的控制器最后整个项目的目录结构![…

ce从初阶到大牛(两台主机免密登录)

一、配置ssh远程连接 实现两台linux主机之间通过公钥验证能够互相实现免密登陆 1.确认服务程序是否安装 rpm -qa | grep ssh 2.是否启动 ps -aux | grep ssh 3.生成非对称公钥 ssh-keygen -t rsa 4.公钥发送到客户端 cd /root/.ssh/ ssh-copy-id root192.168.170.134 因为…

OpenCV学习(五)——图像基本操作(访问图像像素值、图像属性、感兴趣区域ROI和图像边框)

图像基本操作 5. 图像基本操作5.1 访问像素值并修改5.2 访问图像属性5.2 图像感兴趣区域ROI5.3 拆分和合并图像通道5.4 为图像设置边框&#xff08;填充&#xff09; 5. 图像基本操作 访问像素值并修改访问图像属性设置感兴趣区域&#xff08;ROI&#xff09;分割和合并图像 …

本来打算做功能测试的,但是发现playwright太好玩了,玩了一天,功能测试进度为空

本文是作者的自言自语&#xff1a;//todo 未完待续 https://blog.csdn.net/lineuman 微软果然有大牛啊&#xff01;有能人的公司总是令人敬佩。 playwright这种级别的工具简直就是核弹级别的。 当我开始使用playwright的时候&#xff0c;嘭的一下&#xff0c;我的世界炸了&…

javaEE -10(11000字详解5层重要协议)

一&#xff1a;应用层重点协议 1.1&#xff1a; DNS DNS&#xff0c;即Domain Name System&#xff0c;域名系统。DNS是一整套从域名映射到IP的系统。 TCP/IP中使用IP地址来确定网络上的一台主机&#xff0c;但是IP地址不方便记忆&#xff0c;且不能表达地址组织信息&#x…

2023年正版win10/win11系统安装教学(纯净版)

第一步&#xff1a;准备一个8G容量以上的U盘。 注意&#xff0c;在制作系统盘时会格式化U盘&#xff0c;所以最好准备个空U盘&#xff0c;防止资料丢失。 第二步&#xff1a;制作系统盘。 安装win10 进入windows官网 官网win10下载地址&#xff1a;https://www.microsoft.c…

Oracle数据库设置归档模式(超级简单)

1、打开监听 查看监听的状态&#xff0c;如果没打开监听需要打开监听&#xff0c;如果打开直接下一步 lsnrctl status 打开监听 lsnrctl start 2、启动数据库 首先进入数据库 sqlplus /nolog 然后连接管理员 conn / as sysdba 3、查看当前模式 archive log list 可以…

图像特征Vol.1:计算机视觉特征度量|第一弹:【纹理区域特征】

目录 一、前言二、纹理区域度量2.1&#xff1a;边缘特征度量2.2&#xff1a;互相关和自相关特征2.3&#xff1a;频谱方法—傅里叶谱2.4&#xff1a;灰度共生矩阵(GLCM)2.5&#xff1a;Laws纹理特征2.6&#xff1a;局部二值模式&#xff08;LBP&#xff09; 一、前言 &#x1f…

【C++】C++入门(下)--内联函数 auto关键字 nullptr

目录 一 内联函数 1 内联函数概念和定义 2 内联函数特性 二 auto关键字 1 auto概念 2 auto 的使用细则 (1) auto与指针和引用结合起来使用 (2) 在同一行定义多个变量 3 auto不能推导的场景 (1) auto不能作为函数的参数 (2) auto不能直接用来声明数组 4 基于范围的fo…

使用 excel 快速拼接省市区镇街村居五级区划完整名称

你知道的越多&#xff0c;你不知道的越多 点赞再看&#xff0c;养成习惯 如果您有疑问或者见解&#xff0c;欢迎指教&#xff1a; 企鹅&#xff1a;869192208 文章目录 前言数据准备excel 函数附件 前言 之前做了国家区划的映射关系&#xff0c;在其过程中&#xff0c;使用代码…

JAVA设计模式详解(独家AI解析)

JAVA设计模式详解&#xff08;独家AI解析&#xff09; 一、JAVA介绍二、JAVA设计模式六大原则三、JAVA设计模式介绍四、JAVA设计模式详解4.1 单例模式4.1.1 懒汉式&#xff08;Lazy Initialization&#xff09;4.1.2 饿汉式&#xff08;Lazy Initialization&#xff09; 4.2 代…

Kubernetes - Ingress HTTP 负载搭建部署解决方案(新版本v1.21+)

在看这一篇之前&#xff0c;如果不了解 Ingress 在 K8s 当中的职责&#xff0c;建议看之前的一篇针对旧版本 Ingress 的部署搭建&#xff0c;在开头会提到它的一些简介Kubernetes - Ingress HTTP 负载搭建部署解决方案_放羊的牧码的博客-CSDN博客 开始表演 1、kubeasz 一键安装…

【3D 图像分割】基于 Pytorch 的 VNet 3D 图像分割6(数据预处理)

由于之前哔站作者整理的LUNA16数据处理方式过于的繁琐&#xff0c;于是&#xff0c;本文就对LUNA16数据做一个新的整理&#xff0c;最终得到的数据和形式是差不多的。但是&#xff0c;主要不同的是代码逻辑比较的简单&#xff0c;便于理解。 对于数据集的学习&#xff0c;可以…

在3分钟内使用AI-Chat生成精美PPT(附AI工具)

前言 在人工智能的大趋势下&#xff0c;AI-Chat是一款令人惊叹的技术。它用强大的自然语言处理技术帮助我们快速生成PPT&#xff0c;提高工作效率。本文将介绍使用ChatAI-Chat生成PPT的方法&#xff0c;以及使用Mindshow转换为炫酷的演示文稿。让技术为我们节省时间&#xff0c…

cuda卸载

去查看你的电脑显卡对应的cuda版本&#xff0c;不然还是一整个用不到gpu的情况嘿嘿. 啊啊啊啊打开控制面板看一下&#xff0c;驱动不要乱卸载&#xff1a; 这些东西不能全部卸载了哦&#xff0c;只能卸载含有“CUDA”的那几个&#xff08;其实其他的可能也没有用 但是不懂的哇 …

SpringMVC Day 05 : Spring 中的 Model

前言 欢迎来到 SpringMVC 系列教程的第五天&#xff01;在之前的教程中&#xff0c;我们已经学习了如何使用控制器处理请求和返回视图。今天&#xff0c;我们将深入探讨 Spring 中的 Model。 在 Web 应用程序开发中&#xff0c;数据的传递和展示是非常重要的。SpringMVC 提供…

docker部署prometheus+grafana服务器监控(三) - 配置grafana

查看 prometheus 访问 http://ip:9090/targets&#xff0c;效果如下&#xff0c;上面我们通过 node_exporter 收集的节点状态是 up 状态。 配置 Grafana 访问 http://ip:3000&#xff0c;登录 Grafana&#xff0c;默认的账号密码是 admin:admin&#xff0c;首次登录需要修改…

SpringMVC Day 01:入门案例

前言 在我们的日常工作和学习中&#xff0c;Web 开发是一个无法回避的重要环节。而在 Java Web 开发领域&#xff0c;SpringMVC 无疑是一个重量级选手。它以其灵活性、强大功能和清晰的 MVC 结构&#xff0c;赢得了大量开发者的青睐。但是&#xff0c;对于初学者来说&#xff…

PTE-写作 学习(一)

目录 PTE写作 写作技能 词汇积累 熟悉机经 pte写作考的就是态度 写作技能 看一段写一句 蓝色框里的单词是不可以使用的 &#xff0c;他们是副词&#xff0c;要添加新的句子 PTE写作 写作技能 词汇积累 熟悉机经 题库太窄 pte写作考的就是态度 写作技能 极有模板可…