深入理解JMM以及并发三大特性(1)

文章目录

    • 1. 并发与并行
    • 2. JMM
    • 3. 并发三大特性
    • 4.总结

1. 并发与并行

并行:指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是宏观来看,二者都是一起执行的。
并发:指在同一时刻只能有一个指令执行,但多个进程指令被快速的轮换执行,使得宏观上具有多个进程同时执行的效果,但在围观上并不是同时执行,只是把时间分为若干段,使多个进程快速交替执行。

目标都是最大化CPU的使用率

2. JMM

JMM也就是java内存模型,它和JVM的内存模型不是一个概念,JMM是与线程间通信有关的,并且它是一块共享内存模型。Java虚拟机规范中定义了Java内存模型(Java Memory Model,JMM),用于屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果,JMM规范了Java虚拟机与计算机内存是如何协同工作的:规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。JMM 描述的是一种抽象的概念,一组规则,通过这组规则控制程序中各个变量在共享数据区域和私 有数据区域的访问方式,JMM是围绕原子性、有序性、可见性展开的。
在这里插入图片描述
一个线程是如何修改共享变量的?首先线程需要从主存中拷贝一个副本到自己的工作内存的栈帧中,然后进行修改,修改后再推送到主内存。(实际上是本地内存和主内存的交互)

3. 并发三大特性

并发编程一些问题的源头都来自于并发编程的三大特性,即:可见性、原子性和有序性。

  • 可见性

当一个线程修改了共享变量值,其他线程能够看到修改的值。Java内存模型时通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方法来实现可见性。

如何保证可见性

  1. 通过volatile关键字保证可见性
  2. 通过内存屏障保证可见性
  3. 通过sychronized保证可见性
  4. 通过Lock保证可见性
  5. 通过final关键字保证可见性

如下面代码:

public class VisibilityTest {
    private boolean flag = true;
    private int count = 0;
    public void refresh() {
        flag = false;
     System.out.println(Thread.currentThread().getName() + "修改flag:"+flag);
    }

public void load() {
        System.out.println(Thread.currentThread().getName() + "开始执行.....");
        while (flag) {
            //TODO  业务逻辑
            count++;
        }
        System.out.println(Thread.currentThread().getName() + "跳出循环: count=" + count);
    }

    public static void main(String[] args) throws InterruptedException {
        VisibilityTest test = new VisibilityTest();

        // 线程threadA模拟数据加载场景
        Thread threadA = new Thread(() -> test.load(), "threadA");
        threadA.start();

        // 让threadA执行一会儿
        Thread.sleep(1000);
        // 线程threadB通过flag控制threadA的执行时间
        Thread threadB = new Thread(() -> test.refresh(), "threadB");
        threadB.start();

    }


    public static void shortWait(long interval) {
        long start = System.nanoTime();
        long end;
        do {
            end = System.nanoTime();
        } while (start + interval >= end);
    

线程A调用load方法不断修改count的值,线程B修改flag来终止线程A的循环,但由于JMM的关系,线程B对flag的修改线程A看不到(线程A会一直使用其副本,不会读主存的新值),就一直会死循环,这就是可见性问题导致的。
在这里插入图片描述

volatile关键字

private volatile boolean flag = true;

volatile底层是由c++实现的。如下面Hotspot源码,它首先判断flag是不是valatile关键字修饰的,如果是就调用后面的storeload()方法(这个本质上就是内存屏障,这是JVM层面的内存屏障)。
在这里插入图片描述
进入storeload方法,由于java的跨平台特性,jvm底层对不同的系统架构提供了不同的storeload的实现,这里我们看x86架构。storeload方法会调用fence方法,然后fence方法执行了一句lock; addl $0,0(%%rsp),这是一条汇编指令,这条指令称为lock前缀指令。下面介绍一下这条指令的作用:

  1. 确保后续指令执行的原子性。在Pentium及之前的处理器中,带有lock前缀的指令在执行期间会锁住总线,使得其它处理器暂时无法通过总线访问内存,很显然,这个开销很大。在新的处理器中,Intel使用缓存锁定来保证指令执行的原子性,缓存锁定将大大降低 lock前缀指令的执行开销。
  2. LOCK前缀指令具有类似于内存屏障的功能,禁止该指令与前面和后面的读写指令重排序。
  3. LOCK前缀指令会等待它之前所有的指令完成、并且所有缓冲的写操作写回内存(也就是将storebuffer中的内容写入内存)之后才开始执行,并且根据缓存一致性协议,刷新 storebuffer的操作会导致其他cache中的副本失效(缓冲失效)。

这就是Volatile实现可见性和有序性的原理。
在这里插入图片描述
上面代码介绍了字节解释器执行volatile关键字的过程,字节解释器用C++实现了JVM指令,其优点是实现相对简单且容易理解,缺点是执行慢。由于字节码解释器执行速度很慢,所以jvm基本不采用上述方式来执行代码,而是使用模版解释器(可以理解为c++的JIT技术),模版解释器对每个指令都写了一段对应的汇编代码,启动时将每个指令与对应的汇编代码入口绑定,可以说效率达到了极致,底层还是加了lock; addl $0,0(%%rsp)汇编指令,我们从汇编指令可以看出来。
在这里插入图片描述

内存屏障

public void load() {
        while (flag) {
            //TODO  业务逻辑
            count++;
        }
        //这里就设置了内存屏障
       UnsafeFactory.getUnsafe().storeFence();     
    }

UnsafeFactory.getUnsafe().storeFence();底层和volatile一样,都是调用了storeload和fence方法,即加入了lock前缀指令。
利用本地变量自动淘汰机制

public void load() {
        while (flag) {
            //TODO  业务逻辑
            count++;
            shortWait(1000000);
        }
       
    }

调用shortWait后,线程A会陷入等待,然后过一段时间后,本地副本的flag会被淘汰(需要达到一定的时间,时间过短是不会被淘汰的),所以while(flag)再次取flag的值会从主存中取,所以会线程A会被结束,这也提供了一种解决可见性的思路。

利用Thread.yield()方法

Thread.yield() 是一个静态方法,它的目的是让当前线程让出 CPU 资源,使得其他具有相同优先级的线程有机会运行。调用 Thread.yield() 将当前线程从运行状态转为就绪状态,然后重新竞争 CPU 资源。在 Java 中,Thread.yield() 的底层实现可以因平台而异,因为它依赖于底层操作系统的线程调度机制。一般来说,Thread.yield() 可能会调用底层操作系统提供的让出 CPU 资源的原语。在许多系统中,这可能是通过设置线程的状态为就绪状态并将其放置在就绪队列中来实现的。然后,操作系统的调度器可以选择从就绪队列中选择另一个线程来运行。

Thread.yield()会让线程A让出当前的CPU资源(时间片),这个过程会发生线程状态转换(线程上下文切换),线程进行上下文切换的时候会保存当前线程的线程,如果线程A又竞争到CPU时间片,它就需要需要还原自己的线程,这个时候线程A就需要重写加载自己的上下文,所以可以从主存加载到修改后的flag值,这就解决了可见性问题。上下文切换的图解为:

public void load() {
        while (flag) {
            //TODO  业务逻辑
            count++;
            Thread.yield();
        }
       
    }

加入标准输出流

public void load() {
        while (flag) {
            //TODO  业务逻辑
            count++;
            System.out.println();
        }
       
    }

System.out.println();为什么可以实现flag的可见性,这就和System.out.println();底层有关系了:

public void println(char x[]) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

可以看见println方法底层加入了重量级锁synchronized,而它本身就是可以保证原子性、可见性和有序性的。其实synchronized底层也是靠内存屏障来实现的,同样会调用storeload和fence来加入内存屏障来实现可见性的。

使用unpark

public void load() {
        while (flag) {
            //TODO  业务逻辑
            count++;
			LockSupport.unpark(Thread.currentThread);
        }
       
    }

其实它本质上也是内存屏障

使用sleep()方法

public void load() {
        while (flag) {
            //TODO  业务逻辑
            count++;
             Thread.sleep(1000);
        }
       
    }

我们看sleep底层源码

在这里插入图片描述
其实它底层也是调用了fence方法,加入内存屏障,所以同样它也可以实现可见性。

使用Integer关键字

private Integer count = 0;

我没看底层的Integer源码,我们知道Integer会定义一个value值来保存需要保证的int值,而这个值本身是一个final常量,而jvm也对final做了优化,即保证fianl的可见性(猜测底层也是使用了内存屏障)。

4.总结

JMM实现共享变量的可见性的方式有两种,第一调用storeload方法加入内存屏障来实现可见性(针对x86架构),第二种方式就是上下文切换重新从共享内存加值新的值(yield方法)。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2023年【道路运输企业安全生产管理人员】模拟试题及道路运输企业安全生产管理人员作业考试题库

题库来源:安全生产模拟考试一点通公众号小程序 2023年道路运输企业安全生产管理人员模拟试题为正在备考道路运输企业安全生产管理人员操作证的学员准备的理论考试专题,每个月更新的道路运输企业安全生产管理人员作业考试题库祝您顺利通过道路运输企业安…

FreeRTOS源码阅读笔记5--mutex

互斥量是一种特殊的二值信号量,拥有优先级继承的机制,所以适合用在临界资源互斥访问。 5.1创建互斥量xSemaphoreCreateMutex() 5.1.1函数原型 5.1.2函数框架 5.2创建递归互斥量xSemaphoreCreateRecursiveMutex() 5.2.1函数原型 5.2.2函数框架 xSemaph…

金融众筹模式系统源码 适合创业孵化机构+天使投资机构+投资基金会等 附带完整的搭建教程

随着互联网技术的发展和金融市场的开放,金融众筹模式逐渐成为一种新型的融资方式。这种模式通过互联网平台聚集大量投资者,共同参与到一个项目中,为项目提供资金支持,最终获得投资回报。今天罗峰给大家分享一款金融众筹模式系统源…

app小程序开发的重点在哪里?|企业软件定制网站建设

app小程序开发的重点在哪里?|企业软件定制网站建设 App小程序定制开发是近年来快速发展的一项技术服务,随着移动互联网的普及和用户需求的不断升级,越来越多的企业和个人开始关注和需求定制化的小程序开发。那么,对于app小程序定制…

OAuth2认证请求头中的authorization从哪里来的

Oauth2的登录接口oauth/token, 在请求头里有个 authorization: Basic dGVzdENsaWVudElkOnRlc3RDbGllbnRTZWNyZXQ 其中 dGVzdENsaWVudElkOnRlc3RDbGllbnRTZWNyZXQ 是 client-id:client-secret 的base64的编码 例如项目中的配置是(这个东西一般在网关里配置的): security:oau…

搜维尔科技:Faceware面部捕捉最佳实践!

视频源和分辨率: 我们的软件针对 RGB 彩色素材进行了优化,不支持使用红外摄像机。 我们建议视频分辨率为 720p 和 1080p。低于 720p 的分辨率可能会对跟踪质量产生负面影响,而高于 1080p 的分辨率会导致存储要求和传输时间增加,而…

程序员最奔溃的瞬间

身为程序员哪一个瞬间让你最奔溃? *程序员最奔溃的瞬间, 勇士? or 无知?

经典百搭女童加绒卫衣,看的见的时尚

经典版型套头卫衣 宽松百搭不挑人穿 单穿内搭都可以 胸口处有精美的小熊印花 面料是复合柔软奥利绒 暖和又不显臃肿哦!!

提升编程效率:软件工程师必备的10个Git命令

本文翻译自 10 Must-Know Git Commands for Software Engineers,作者: Rabi Siddique, 略有删改。 Git和GitHub是每个软件工程师必须知道的最基本的东西。这些工具是开发人员日常工作的组成部分,因为我们每天都与它们互动。熟练掌…

【excel技巧】excel表格如何转换为word

Excel表格想要转换到word文档中,直接粘贴复制的话,可能会导致表格格式错乱,那么如何转换才能够保证表格不错乱?今天分享两个方法,excel表格转换到word文件。 方法一: 首先打开excel表格,将表格…

Leetcode—1410.HTML实体解析器【中等】

2023每日刷题(三十八) Leetcode—1410.HTML实体解析器 算法思想 实现代码 typedef struct entityChar {char* entity;char rechar; }entity;entity matches[] {{""", "},{"'", \},{"&"…

解决“yarn : 无法加载文件 C:Progr Files\nodejs yarn.ps1,因为在此系统上禁止运行脚本的问题-使用命令更改计算机的执行策略

安装完成之后再配置环境变量 npm install -g yarn但是在终端上输入yarn -v命令之后会报这个错 解决方案: 1.管理员运行powershell 2.输入set-ExecutionPolicy RemoteSigned之后再输入Y set-ExecutionPolicy RemoteSigned*3.查看执行策略:get-Executi…

Vue弹窗的使用与传值

使用element-UI中的Dialog 对话框 vue组件结合实现~~~~ 定义html <div click"MyAnalyze()">我的区划</div><el-dialog title"" :visible.sync"dialogBiomeVisible"><NationalBiome :closeValue"TypeBiome" cl…

【开源】基于JAVA的高校实验室管理系统

项目编号&#xff1a; S 015 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S015&#xff0c;文末获取源码。} 项目编号&#xff1a;S015&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 实验室类型模块2.2 实验室模块2.3 实…

适用于电脑的5个免费文件恢复软件分享

适用于电脑的最佳免费文件恢复软件 任何计算机用户都可能经历过丢失重要文件的恐惧。重要数据的丢失可能会令人不安和沮丧&#xff0c;无论是由于不小心删除、计算机故障还是硬盘格式化造成的。幸运的是&#xff0c;在数字时代&#xff0c;您可以使用值得信赖的解决方案检索这些…

【代码随想录】算法训练计划30

【代码随想录】算法训练计划30 1、51. N 皇后 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;…

部署项目时常用的 Linux 命令

目录 1 前言2 SSH登录命令3 SCP传输命令4 CP拷贝命令5 MV移动命令6 TAR解压命令7 DU查看文件夹/文件大小8 TAIL查看日志9 NOHUP后台运行10 结语 1 前言 在应用部署过程中&#xff0c;Linux命令是必不可少的工具。它们能够帮助我们管理文件、连接服务器、拷贝文件、查看日志以及…

基于SpringBoot的图书管理系统

基于SpringBoot的图书管理系统 图书管理系统开发技术功能模块代码结构数据库设计运行截图源码获取 图书管理系统 开发技术 技术&#xff1a;SpringBoot、MyBatis-Plus、MySQL、Beetl、Layui。 框架&#xff1a;基于开源框架Snowy-Layui开发。 工具&#xff1a;IDEA、Navicat等…

越南服务器租用:企业在越南办工厂的趋势与当地(ERP/OA等)系统部署的重要性

近年来&#xff0c;越南逐渐成为全球企业布局的热门目的地之一。许多企业纷纷选择在越南设立工厂&#xff0c;以利用其低廉的劳动力成本和优越的地理位置。随着企业在越南的扩张&#xff0c;对于当地部署ERP系统或OA系统等的需求也日益增长。在这种情况下&#xff0c;租用越南服…

PTA-矩阵A乘以B

给定两个矩阵A和B&#xff0c;要求你计算它们的乘积矩阵AB。需要注意的是&#xff0c;只有规模匹配的矩阵才可以相乘。即若A有Ra​行、Ca​列&#xff0c;B有Rb​行、Cb​列&#xff0c;则只有Ca​与Rb​相等时&#xff0c;两个矩阵才能相乘。 输入格式&#xff1a; 输入先后…