并发编程-Java内存模型

news2024/12/22 13:35:51

Java内存模型

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

1)多线程之间如何通信(线程之间以何种机制来交换数据)

2)多线程之间如何同步(控制不同线程间操作发生的相对顺序)

线程之间常用的通信机制有两种:共享内存和消息传递,Java采用的是共享内存模型

Java内存模型的抽象结构

Java线程之间的通信由Java内存模型(Java Memory Model,简称JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。

JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在,它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

根据JMM的规定,线程对共享变量的所有操作都必须在自己的本地内存中进行,不能直接从主内存中读取。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操作之前,必须先执行过了load和assign操作

  • 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现

  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值

  • 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量

  • 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)

Java中可见性底层有两种实现:

1、内存屏障    (synchronized  Threed.sleep(10)  volatile)
lock addl $0x0,(%rsp)
2、cpu上下文切换    (Threed.yield()   Threed.sleep(0))

锁的内存语义

锁获取和释放的内存语义:

  • 当线程获取锁时,JMM会把该线程对应的本地内存置为无效。

  • 当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。

synchronized关键字的作用是确保多个线程访问共享资源时的互斥性和可见性。在获取锁之前,线程会将共享变量的最新值从主内存中读取到线程本地的缓存中,释放锁时会将修改后的共享变量的值刷新到主内存中,以保证可见性。

volatile内存语义

volatile写:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。

volatile读:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量。

volatile内存语义的实现原理

JMM属于语言级的内存模型,它确保在不同的编译器和不同的处理器平台之上,通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保证。

volatile禁止重排序规则

为了实现volatile的内存语义,JMM会限制编译器重排序,JMM针对编译器制定了volatile重排序规则表。

volatile禁止重排序场景:

1、当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。

2、当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。

3、当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。

有序性案例深入分析

// 在Java多线程程序中,有时候需要采用延迟初始化来降低初始化类和创建对象的开销。
// 双重检查锁定是常用的延迟初始化技术,但它有一个错误的用法。
public class Singleton
{
    private static Singleton singleton;
    
    private Singleton()
    {
    }
    
    /**
     * 双重检查锁定(Double-checked Locking)实现单例对象的延迟初始化
     */
    public static Singleton getSingleton()
    {
        if (singleton == null)
        {
            synchronized (Singleton.class)
            {
                if (singleton == null)
                {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

正确用法:
private volatile static Singleton singleton; //添加volatile关键字

原因分析:
singleton = new Singleton(); //执行过程分为如下三步
1、分配对象内存空间,memory = allocate()
2、初始化对象,ctorInstance(memory)
3、设置instance指向刚刚分配的内存地址,instance = memory
注意:上面的第2步和第3步,可能发生重排序,即3发生在2的前面。导致可能获得未初始化的对象发生错误

JMM内存屏障插入策略

为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

JMM内存屏障插入策略:

1、在每个volatile写操作的前面插入一个StoreStore屏障

2、在每个volatile写操作的后面插入一个StoreLoad屏障

3、在每个volatile读操作的后面插入一个LoadLoad屏障

4、在每个volatile读操作的后面插入一个LoadStore屏障

注意:x86不会对读-读、读-写、写-写操作做重排序,仅会对写-读操作做重排序。

不同硬件实现内存屏障的方式不同,Java内存模型屏蔽了这种底层硬件平台的差异,由JVM来为不同的平台生成相应的机器码。

Hotspots源码中内存屏障的实现
inline void OrderAccess::storeload()  { fence(); }
inline void OrderAccess::fence() {
  if (os::is_MP()) {
    // always use locked addl since mfence is sometimes expensive
#ifdef AMD64
    __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
    __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
  }
}

happens-before

happens-before的定义

JSR-133使用happens-before的概念来指定两个操作之间的执行顺序。

  • as-if-serial语义保证单线程内程序的执行结果不被改变

  • happens-before关系保证正确同步的多线程程序的执行结果不被改变。

happens-before规则

JSR-133规范定义了如下happens-before规则:

1)程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作;

2)锁定规则:对一个锁的解锁,happens-before于随后对这个锁的加锁;

3)volatile变量规则:对一个volatile变量的写操作,happens-before于任意后续对这个volatile变量的读操作;

4)传递规则:如果A happens-before B,并且B happens-before C,则A happens-before C;

5)线程启动规则:如果线程A调用线程B的start()方法来启动线程B,则start()操作happens-before于线程B中的任意操作;

6)线程中断规则:对线程interrupt()方法的调用happens-before于被中断线程的代码检测到中断事件的发生;

7)线程终结规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回;

8)对象终结规则:一个对象的初始化完成happens-before于它的finalize()方法的开始。

总结

Java中的volatile关键字可以保证多线程操作共享变量的可见性以及禁止指令重排序,synchronized关键字不仅保证可见性,同时也保证了原子性(互斥性)。在更底层,JMM通过内存屏障来实现内存的可见性以及禁止重排序。为了程序员的方便理解,提出了happens-before,它更加的简单易懂,从而避免了程序员为了理解内存可见性而去学习复杂的重排序规则以及这些规则的具体实现方法。

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

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

相关文章

AMD移动FP5平台时序解释

由于刚开始接触AMD移动平台,难免有错误;如有错误请指出共同进步。 配置如下: APU:FP5 Processor EC: ITE5570 CorePMU:RT3662AC 一、基本知识概括 1、IC pin脚信号解释 (1)一般OK是外部信号(对于IC来说是Input);PG是IC发出的(对于IC来说是Output);AMD开机:EN---…

存储优化知识复习三详细版解析

存储优化 知识复习三 一、 选择题 1、 数据库领域的三位图灵奖得主是( )。 A、C.W.Bachman B、E.F.Codd C、Peter Naur D、James Gray 【参考答案】ABD2、 数据库DB、数据库系统DBS、数据库管理系统DBMS三者之间得关系是( )。 A、DB&#…

linux性能分析(四)性能优化导轮

一 别再让Linux性能问题成为你的绊脚石 ① 学习历程 备注: 跟我的学习经历很相像刚工作时遇到的场景跟我现在一样,深深的无力感驱使我继续前行目标: 性能优化成为我的肌肉记忆,写代码的潜意识 ... ② 常见的问题 ③ 性能问题为什么这么难呢 思考&#xff1a…

【驱动开发】创建设备节点、ioctl函数的使用

一、控制三盏灯的亮灭 头文件: #ifndef __HEAD_H__ #define __HEAD_H__ typedef struct{unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int ODR; }gpio_t; #define PHY_LED1_ADDR 0X50006000 #def…

java读取指定文件夹下的全部文件,并输出文件名,文件大小,文件创建时间

import java.io.IOException; import java.nio.file.*; import java.nio.file.attribute.*; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { try { Path startingDir Paths.get("你的目…

带你了解如何防御DDoS攻击

DDoS攻击的类型和方法 分布式拒绝服务攻击(简称DDoS)是一种协同攻击,旨在使受害者的资源无法使用。它可以由一个黑客组织协同行动,也可以借助连接到互联网的多个受破坏设备来执行。这些在攻击者控制下的设备通常称为僵尸网络。 …

人工智能、机器学习、深度学习的区别

人工智能涵盖范围最广,它包含了机器学习;而机器学习是人工智能的重要研究内容,它又包含了深度学习。 人工智能(AI) 人工智能是一门以计算机科学为基础,融合了数学、神经学、心理学、控制学等多个科目的交…

表白墙完善(数据库,前端,后端Servlet),再谈Cookie和Session。以及一个关于Cookie的练习小程序

目录 表白墙引入数据库 再谈Cookie和session 得到Cookie ​编辑 设置Cooie 使用Cookie编写一个登入的小界面 表白墙引入数据库 1.先引入数据库的依赖(驱动包),5.1.49 pom.xml中,在之前的两个之前,再去添加一个 &…

百度松果20231022作业

越狱 盒子与球 斯特林第二类数(用dp求)*盒子的阶乘 int dp[11][11]; //n>k int A(int x){int res1;fer(i,2,x1)res*i;return res; } signed main(){IOS;dp[2][1]dp[2][2]dp[1][1]1;fer(i,3,11){dp[i][1]1;fer(j,2,i){dp[i][j]j*dp[i-1][j]dp[i-1][j-…

搜一搜文章排名上首页无压力,优化这5点助你稳步上升

随着微信搜一搜的普及,如何提高文章在搜一搜中的排名已经成为许多公众号运营者关注的重点。高质量的搜一搜排名不仅可以带来更多的阅读量,也是提升公众号影响力的关键。那么,如何做才能轻松实现搜一搜文章上首页排名呢?更多搜一搜文章排名相关,可某薇找我名字。只需…

大规模语言模型人类反馈对齐--PPO算法代码实践

在前面的章节我们已经知道,人类反馈强化学习机制主要包括策略模型、奖励模型、评论模型以及参考模型等部分。需要考 虑奖励模型设计、环境交互以及代理训练的挑战, 同时叠加大语言模型的高昂的试错成本。对于研究人员来说, 使用人类反馈强化学…

数字孪生智慧建筑可视化系统,提高施工效率和建造质量

随着科技的不断进步和数字化的快速发展,数字孪生成为了建筑行业的一个重要的概念,被广泛应用于智能化建筑的开发与管理中。数字孪生是将现实世界的实体与数字世界的虚拟模型进行连接和同步,从而实现实时的数据交互和模拟仿真。数字孪生在建筑…

【Java 进阶篇】深入了解 Bootstrap 插件

Bootstrap 是一个流行的前端框架,提供了各种强大的插件,用于增强网页和应用程序的功能和交互性。本篇博客将深入介绍 Bootstrap 插件,适用于那些刚刚开始学习前端开发的小白。 什么是 Bootstrap? 在深入探讨 Bootstrap 插件之前…

QListWiget和QToolButton

1.简介 Qt 中用于项(Item)处理的组件有两类,一类是 Item Views,包括 QListView、QTreeView、 QTableView、QColumnView 等;另一类是 Item Widgets,包括 QListWidget、QTreeWidget 和 QTable Widget。 Ite…

手机知识:安卓内存都卷到24GB了,为何iPhone还在固守8GB

目录 一、系统机制 二、生态差异 三、总结 在刚刚过去的9月,年货iPhone 15系列正式发布,标准版不出意外还是挤药膏,除了镜头、屏幕有些升级,芯片用iPhone 14 Pro系列的,内存只有6GB;即使是集钛合金机身、…

Json数据上传—>对象转换—>存入MongoDB(SpringData提供的规范)

上传json 代码实现 RestController RequestMapping("/api/hosp") public class ApiController{Autowiredprivate HospitalService hospitalService;PostMapping("saveHospital")public Result saveHosp(HttpServletRequest request){Map<String,String…

Jetpack:015-Jetpack的是脚手架

文章目录 1. 概念介绍2. 使用方法2.1 核心思想2.2 具体内容 3. 示例代码4. 内容总结 我们在上一章回中介绍了Jetpack中小红点相关的内容&#xff0c;本章回中将介绍 脚手架。闲话休提&#xff0c;让我们一起Talk Android Jetpack吧&#xff01; 1. 概念介绍 我们在本章回中介…

【试题038】 逻辑与和赋值表达式例题

1.题目&#xff1a;设int n;&#xff0c;执行表达式(n2)&&(n1)&&(n0)后&#xff0c;n的值是&#xff1f; 2.代码分析&#xff1a; //设int n;&#xff0c;执行表达式(n2)&&(n1)&&(n0)后&#xff0c;n的值是? int main() {int n;printf("…

Java中的static关键字

一、static关键字的用途 在《Java编程思想》P86页有这样一段话&#xff1a; “static方法就是没有this的方法。在static方法内部不能调用非静态方法&#xff0c;反过来是可以的。而且可以在没有创建任何对象的前提下&#xff0c;仅仅通过类本身来调用static方法。这实际上正是s…

C语言 ——宽字符

前言&#xff1a; 过去C语⾔并不适合⾮英语国家&#xff08;地区&#xff09;使⽤。 C语⾔最初假定字符都是单字节的。但是这些假定并不是在世界的任何地⽅都适⽤。 C语⾔字符默认是采⽤ASCII编码的&#xff0c;ASCII字符集采⽤的是单字节编码&#xff0c;且只使⽤了单字节中…