【JMM】保证线程间的可见性,还只知道volatile?

本文目录

前言

举例🌰

情形1 int->Integer

情形2 System.out.println()

情形3  storeFence()

情形4  Thread.yield()

情形5  LockSupport.unpark()

情形6  增长循环内代码执行时间

总结分析

volatile分析

字节码解释器实现

模版解释器实现

其他情形分析


前言

这是一篇关于介绍线程间可见性的文章,当然我们先介绍下什么是可见性( ̄∇ ̄)/

可见性指的是一个线程对共享变量的修改对其他线程是可见的

总所周知,volatile 关键字可以解决能够及时可见的问题,使得修改过的数据能立刻被看到,那么只有加 volatile 关键字才能达到这种效果吗?还有什么情况下线程之间是可见的呢?

我们举一个老生常谈的🌰,代码如下

package com.aqin.custom.aqs;

/**
 * @Description
 * @Author aqin1012 AQin.
 * @Date 5/10/23 3:31 PM
 * @Version 1.0
 */
public class TestVisibility {
   private boolean flag = true;
   private int count = 0;

   public static void main(String[] args) throws InterruptedException {
      TestVisibility testVisibility = new TestVisibility();
      Thread threadA = new Thread(() -> testVisibility.methodA());
      threadA.start();
      Thread.sleep(1000);
      Thread threadB = new Thread(() -> testVisibility.methodB());
      threadB.start();
   }

   public void methodA() {
      System.out.println(Thread.currentThread().getName() + "开始循环♻️ ( ̄∇ ̄)/ ……");
      while (flag) {
         count++;
      }
      System.out.println(Thread.currentThread().getName() + "结束循环♻️ \\(^ω^) ! count=" + count);
   }

   public void methodB() {
      flag = false;
      System.out.println(Thread.currentThread().getName() + "修改 flag: " + flag);
   }
}

上述代码执行时,可以看到,卡住了。。。

我等了很久很久很久。。。还是没跳出循环(见下图)

那么在什么情况下上面的methodA方法会跳出循环,输出System.out.println(Thread.currentThread().getName() + "结束循环♻️ \\(^ω^) ! count=" + count)呢?

给变量count加个volatile关键字修饰下,嘿嘿(如下图),跳出循环了

 关于实现多线程之间的可见性,只有volatile吗?

我们来看看其他会使线程之间可见的情形(。・ω・。)ノ

举例🌰

情形1 int->Integer

当循环内的变量count的类型从int变为Integer

 没错,跳出循环(原因最后统一分析)

情形2 System.out.println()

在循环内部添加一行System.out.println()的输出,可以看到,也会结束循环

情形3  storeFence()

添加内存屏障

情形4  Thread.yield()

使用Thread.yield()方法,让出CPU时间片

情形5  LockSupport.unpark()

使用LockSupportunpark方法

情形6  增长循环内代码执行时间

添加如下方法

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

设置一个较长的停顿时间

 减小停顿时间

来~我们开始分析,我们先从较为熟悉的volatile开始吧

总结分析

volatile分析

volatile保证线程之间的可见性依靠的是内存屏障

我们先介绍下volatile在hotspot虚拟机中的实现

字节码解释器实现

字节码解释器 Byte Code Interpreter,用C++实现了JVM 指令(每个JVM指令,比如volatile,一般都是由一个C++的函数实现的),优点是简单容易理解,缺点是执行慢

在其中字节码解释器实现的volatile方法中,会先对字段类型判断,然后执行下面这行代码

OrderAccess::storeload()

这是其实就是内存屏障,接下来我们简要的从操作系统层面分析下字节码解释器中的这个实现volatile功能的storeload()

在Linux系统x86架构下,storeload()方法会先对处理器进行判断,看是否是多核处理器(因为单核不存在可见性的问题),如果是就会使用lock(汇编指令)来实现volatile的功能(类似内存屏障的功能)

lock前缀指令的作用

  1. 确保后续指令执行的原子性操作

  2. 类似内存屏障的功能(lock前缀指令不是内存屏障的指令)

  3. 确保其他副本失效

模版解释器实现

模版解释器 Template Interpreter 中其实对每一个常用的指令都写了一段汇编代码,启动时将每个指令与对应的汇编代码入口绑定,以提升效率

volatile_barrier(Assembler::Membar_mask_bits(Assembler::StoreLoad | Assembler::StoreStore))

关于StoreLoadStoreStore的理解可以参考下表

屏障类型

指令示例

说明

LoadLoad

Load1;LoadLoad;Load2

保证Load1的读取操作在Load2以及后续读取操作之前执行

StoreStore

Store1;StoreStore;Store2

保证在Store2及其后续写操作执行前,Store1的写操作已经刷新到主内存

LoadStore

Load1;LoadStore;Store2

保证在Store2及其后续写操作执行前,Load1的读操作已经结束

StoreLoad

Store1;StoreLoad;Load2

保证在Load2及其后续的读操作执行前,Store1写操作已经刷新到主内存

不同硬件实现内存屏障的方式不同,Java内存模型屏蔽了底层硬件平台的差异,由JVM来为不同的平台生长相应的机器码,Java中提供了四类内存屏障(上表中的)

内存屏障又称内存栅栏,是一个CPU指令,主要有2个功能:

  • 保证特定操作的执行顺序

  • 保证特定变量的内存可见性(利用该特性实现volatile的内存可见性)

其他情形分析

情形1 中的int->Integer后,会跳出循环的原因是Integer中的intfinal修饰

 在Java虚拟机中,对于使用final关键字修饰的变量或对象引用,在写入操作时会生成相应的内存屏障指令,以确保该变量或对象引用的值对其他线程可见(会在写入操作之后插入一个StoreStore屏障和一个StoreLoad屏障),所以其底层还是通过内存屏障来保证线程之间的可见性的。

情形2 中的System.out.println()println()方法加了synchronized关键字

 在使用synchronized关键字时,JVM会插入不同类型的内存屏障来保证临界区内代码的顺序执行以及变量的可见性和原子性,所以其底层还是通过内存屏障来保证线程之间的可见性的。

情形3 storeFence()就是直接手动添加内存屏障了。

情形4 Thread.yield()是使用Thread.yield()方法让出CPU时间片,即通过上下文切换(需要保存上下文)保证了不同线程之间的可见性。在Java中,从一个线程转到另一个线程,当一个线程被切换出去时,它的本地内存中的数据会被刷新到主内存中,而当另一个线程被切换进来时,它的本地内存会从主内存中加载最新的数据,这个过程保证了不同线程之间的可见性。

需要注意的是,上下文切换虽然可以保证线程之间的可见性,但也会带来一定的开销,因为上下文切换一般需要5-10ms,每次切换都需要将本地内存中的数据刷新到主内存中,这会增加系统的负担。因此,在编写多线程程序时,应该尽量避免过多的上下文切换,以提高程序的性能。

情形5 使用LockSupportunpark方法

 可以看到LockSupport.unpark()底层是通过调用UnSafe类实现的,与情形3⃣️ 不同的是,调用的是unpark()方法,而非storeFence()方法。

情形6 增长循环内代码执行时间,使得缓存过期,再次读取时就会重新去主内存中读取数据,即通过缓存淘汰,保证不同线程之间的可见性。

可以看到其实现不同线程之间的可见性方法有很多,无论是通过内存屏障还是通过上下文切换,其底层原理就是需要满足两点

  • 对线程本地变量的修改可以立刻刷新回主内存

  • 同时使得其他线程中该变量的缓存失效

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

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

相关文章

自动化测试的生命周期是什么?

软件测试发展到今日,已经逐渐标准化且能力更强,其流程每天都在发展。测试人员的技术熟练程度对于整个测试阶段的成功来说至关重要。测试不再意味着仅仅发现错误;它的范围已经扩大,从任何开发项目开始就可以看出它的重要性。 当谈论…

Parker派克伺服电机有哪些优势特点?如何选型?

一、什么是伺服电机? 伺服电机是一种可以通过控制器精确地控制位置、速度和加速度的电机,主要由电机、编码器和控制器三部分组成,具有高转矩、高精度、快速响应和低转速稳定特性,能够在负载扰动、电压变化及机械特性变化下保持较…

分布式搜索引擎es 面试突击

es elastocsearch 倒排索引是在数据查询之前建立,在查询的时候可以直接通过关键词定位到文档内容。用空间换时间 分布式架构原理说一下? es底层是基于lucene来的 大概就是一个用于全文检索的jar包 用es来做分布式的搜索引擎 可以承载一秒钟几千的…

基于分布鲁棒联合机会约束的能源和储备调度(Matlab代码实现)

💥 💥 💞 💞 欢迎来到本博客 ❤️ ❤️ 💥 💥 🏆 博主优势: 🌞 🌞 🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 …

CloudCompare二次开发之如何通过PCL进行点云滤波?

文章目录 0.引言1.CloudCompare界面设计滤波(filter)按钮2.PassThrough直通滤波器3.VoxelGrid体素滤波器4.UniformSampling均匀采样5.StatisticalOutlierRemoval统计滤波器6.RadiusOutlierRemoval半径滤波器7.ConditionRemoval条件滤波器8.ProjectInliers投影滤波器9.ModelOutl…

写给程序员Android Framework 开发,

前言 在 Android 开发者技能中,如果想进大厂,一般拥有较好的学历可能有优势一些。但是如果你靠硬实力也是有机会的,例如死磕Framework。Framework 知识广泛应用在Android各个领域中,重要性显而易见。 成为一名Android Framework…

maven学习总结

生命周期 每个生命周期的各个环节都是由各种插件完成!!!Maven有三个相互独立的生命周期(Maven的这三个生命周期不能看成一个整体)!!! 我们在开发中描述的项目的生命周期&#xff0…

互联网营销之何谓真需求-想知道如何挖掘真需求看这篇就对了

互联网营销思维是以爆品为核心的迭代思维,本文结合“生日蛋糕”、“方便面”、“蜜雪冰城”几个小例子,以及我们具体的工作,展开聊聊什么是“真需求”。 1. 互联网营销和传统营销的区别 1.1 传统的营销思维: “定位4P&#xff0…

如何修复d3dcompiler_47.dll缺失?多种解决方法分享

在使用Windows操作系统的过程中,有时候会遇到d3dcompiler_47.dll缺失的情况。这个问题可能会导致某些应用程序无法正常运行,因此需要及时解决。本文将介绍如何修复d3dcompiler_47.dll缺失的问题。 一.什么是d3dcompiler_47.dll D3dcompiler_47.dll是Di…

基于html+css图展示57

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

前后端图片交互的简易方式

前后端图片交互的简易方式 一、交互方式说明二、前后端具体代码实现前端具体代码实现后端具体代码实现效果 测试结果 一、交互方式说明 在项目的实际开发中,难免会遇到前端需要渲染数据库中保存的图片,那咱知道图片也属于一种文件,不好保存到…

【零基础QQ机器人开发三】程序上云篇

前言:本文为大家带来QQ机器人程序上云的教程,环境搭建请参考下面链接 【0基础QQ机器人开发】基于go-cqhttp的QQ机器人开发教程,仅供自学 【零基础QQ机器人开发二】服务器篇 文章目录 程序Logger类StatuStore类MultiFunc类QQBot类main.py 前言&#xff1a…

高级Web题库

高级Web题库 For ZPT 声明 一切开发旨在学习,请勿用于非法用途 by rick rick 关注 永雏塔菲喵 永雏塔菲喵 选择题 第1题 知识点:CSS 题目:设置text-decoration属性的删除线的值为( )。 选项: A underlin…

Pytest自动化测试框架一些常见的插件

Pytest拥有丰富的插件架构,超过800个以上的外部插件和活跃的社区,在PyPI项目中以“ pytest- *”为标识。 本篇将列举github标星超过两百的一些插件进行实战演示。 插件库地址:http://plugincompat.herokuapp.com/ 1、pytest-html&#xff…

冗余-安全设计的基石

冗余构成原理就是在系统中采用2套中央处理器(CPU)单元,其中1套为工作主机,1套为热备,一旦工作主机发生故障,热备的CPU将自动投入工作,此时热备的CPU变为工作主机,原工作主机故障处理…

a标签属性href的多种写法

众所周知,a标签的最重要功能是实现超链接和锚点。而且,大多数人认为a标签最重要的作用是实现超链接,其实不单单是实现超链接的方法,今天新起点博客就来整理下a标签中href的几种用法。 1、a href“[removed]js_method();” 这是常用…

Android aidl及binder基础知识巩固

作者:义华 1、什么是binder binder是android framework提供的,用于跨进程方法调用的机制,具有安全高效等特点。 我们知道,在 Android 系统中,每个应用程序都运行在一个独立的进程中,各个进程之间需要进行…

chatGPT提问,BGP内容

ChatGPT提问:提问框架 背景角色任务要求 动态路由:内部网关协议:如RIP ISIS OSPF 在同一个公司内部运行的路由协议 外部网关协议:如 BGP 在不同公司之间运行的路由协议 AS:自治系统 每个自治系统都有唯一的…

玩机搞机-----安卓全机型 ADB FAST 各种指令解析说明与操作【二】基础联机

安卓全机型 玩机 搞机 ADB FAST 各种指令解析说明与操作_adb线刷命令_安卓机器的博客-CSDN博客 今天对上个帖子不足的地方进行补正。方便友友进行基础的联机操作,很多时候我们用adb指令的时候会有各种奇奇怪怪的问题。例如同一个机型,同一个指令。有时候…

OpenCL编程指南-4.4矢量操作符

矢量操作符 如下描述了可用于矢量数据类型或矢量和标量数据类型组合的各类操作符。 算术操作符 算术操作符(加()、减(–)、乘(*)和除(/)),可以作用于内置整数、浮点标量和矢量数…