Java并发编程-Java内存模型(JMM)

JUC并发编程第五章——Java内存模型JMM CPU的运行并不是直接操作内存而是先把内存里面的数据读到缓存,而内存的读和写操作的时候会造成不一致的问题。JVM规范中试图定义一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序再各种平台下都能达到一致性的内存访问效果。,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问方式并决定一个线程对共享变量的写入以及如何变成对另一个线程可见,关键技术点都是围绕多线程的。● JMM没有那些特征或者它的三大特征是什么?● 你知道什么是Java内存模型JMM吗? 阅读详情

前言

在上一章 Java并发编程-Android的UI框架为什么是单线程的? 中笔者介绍了并发编程线程安全「三大恶」:「可见性」、「原子性」以及「有序性」

广义上来说,并发编程问题笔者归纳为:是由于后续操作看不到前面操作的结果而引发的

首先「大恶-可见性」顾名思义,即线程B能否看见线程A对共享变量的操作结果

其次「二恶-原子性」CPU 指令/操作被中断/分割,广义上看笔者认为也是可见性问题,比如线程 A 修改共享变量 x += 1 时被中断,去执行线程 B x = 40 ,线程 A 恢复执行时可能看不到 x 已经被修改为 40

最后「三恶-有序性」指的是程序按照代码的先后顺序执行,即先执行 x = 40 ,后续读取 x == 40 一定为真,如果有各种优化导致指令重排序,后续读取 x == 40 时可能不为真

总而言之,并发编程的问题大部分都是「可见性」问题

Java内存模型(JMM)

在上一章中笔者介绍了导致「可见性」的原因是缓存,导致「有序性」的原因是各种优化(编译时、处理器以及运行时),首先笔者想到的是禁用缓存和禁止各种优化操作,在 Java 中如何禁用缓存和禁止各种优化呢?

Happens-Before

Java 内存模型是通过各种操作来定义的1,包括对变量的读 / 写操作,监视器锁的加锁和释放操作,以及线程的启动和合并操作。JMM 为程序中所有的操作定义了一个偏序关系2,称之为 Happens-Before。要想保证执行操作 B 的线程看到操作 A 的结果(无论 A 和 B 是否在同一个线程中执行),那么在 A 和 B 之间必须满足 Happens-Before 关系。如果两个操作之间缺乏 Happens-Before 关系,那么 JVM 可以对它们任意的重排序。

Happens-Before 的规则包括:

规则一:程序顺序

这条规则比较符合我们的直觉,在单线程中代码的顺序即程序的执行顺序:int x = 40 将在 int y = 41 之前执行

public void test() {
    int x = 40;
    int y = 41;
}
规则二:监视器锁3

监视器锁即内置锁(synchronized),在监视器锁上的解锁操作必须在同一个监视器锁的加锁操作之前执行,这条规则比较好理解,现实世界中的锁在关锁前必须处于打开状态

image-20220525142109393

规则三:volatile 变量45

对 volatile 变量的写入操作必须在对该变量的读取操作之前执行。

这条规则不大好理解,以下代码与文字摘自 深入理解Java内存模型(四)——volatile

理解 volatile 特性的一个好方法是:把对 volatile 变量的单个读/写,看成是使用同一个监视器锁对这些单个读/写操作做了同步6

class VolatileFeaturesExample {
    volatile long vl = 0L;  // 使用 volatile 声明 64 位的 long 型变量

    public void set(long l) {
        vl = l;   // 单个 volatile 变量的写
    }

    public void getAndIncrement () {
        vl++;    // 复合(多个)volatile 变量的读 / 写
    }

    public long get() {
        return vl;   // 单个 volatile 变量的读
    }
}

假设有多个线程分别调用上面程序的三个方法,这个程序在语意上和下面程序等价:

class VolatileFeaturesExample {
    long vl = 0L;               // 64 位的 long 型普通变量

    public synchronized void set(long l) {     // 对单个的普通 变量的写用同一个监视器同步
        vl = l;
    }

    public void getAndIncrement () { // 普通方法调用
        long temp = get();           // 调用已同步的读方法
        temp += 1L;                  // 普通写操作
        set(temp);                   // 调用已同步的写方法
    }
    
    public synchronized long get() { 
        return vl; // 对单个的普通变量的读用同一个监视器同步
    }
}

如上面示例程序所示,对一个 volatile 变量的单个读 / 写操作,与对一个普通变量的读 / 写操作使用同一个监视器锁来同步,它们之间的执行效果相同。

从内存语义的角度来说,volatile 与监视器锁有相同的效果:volatile 写和监视器的释放有相同的内存语义;volatile 读与监视器的获取有相同的内存语义。

简而言之,volatile 变量自身具有下列特性:

  • 可见性。对一个 volatile 变量的读,总是能看到(任意线程)对这个 volatile 变量最后的写入。
  • 原子性:对任意单个 volatile 变量的读 / 写具有原子性,但类似于 volatile++ 这种复合操作不具有原子性。
规则四:线程启动

在线程上对 Thread.start 的调用必须在该线程中执行任何操作之前执行。

这条规则比较好理解,即线程 A 启动操作先执行,然后再执行线程 A 内的操作,换言之,线程 A 启动前的操作结果对线程 A 中的操作可见,参考下面的示例代码:

Thread thread = new Thread(() -> {
    // 线程中的操作, 可以看到线程启动前对共享变量的操作结果
    System.out.println(x);
});

// 线程启动前操作共享变量
x = 10;

// 线程启动
thread.start();
规则五:线程结束

线程中的任何操作都必须在其他线程检测到该线程已经结束之前执行,或者从 Thread.join 中成功返回,或者在调用 Thread.isAlive 时返回 false。

这条规则也比较好理解,即线程结束前,线程内的任何操作都已经执行,换言之,线程结束后,线程内的操作结果对后续操作可见,参考下面的示例代码:

Thread thread = new Thread(() -> {
    // 线程中的操作, 可以看到线程启动前对共享变量的操作结果
    System.out.println(x);

    // 线程中操作共享变量
    x = 20;
});

// 线程启动前操作共享变量
x = 10;

// 线程启动
thread.start();

// 线程join, 等待线程结束
thread.join();

// 线程中对共享变量的操作结果此处可见
System.out.println(x);
规则六:线程中断

当一个线程在另一个线程上调用 interrupt 时,必须在被中断线程检测到 interrupt 调用之前执行(通过抛出 InterruptedException,或者调用 isInterrupted 和 interrupted)。

这条规则笔者不太理解,只是理解了表面意思即:线程 A 对线程 B interrupt 的调用,必须在线程 B 检测到中断事件之前执行。

规则七:终结器

对象的构造函数必须在启动该对象的终结器7之前完成。

这条规则好理解,可以理解为 Android Activity 的生命周期,这里说的构造函数与终结器就是对象的生命周期,调用终结器之前,对象必须已经初始化。

规则八:传递性

如果操作 A 在操作 B 之前执行,并且操作 B 在操作 C 之前执行,那么操作 A 必须在 操作 C 之前执行。

看到这条规则,笔者竟然首先想到了《秦时明月》中《白马非马》辩论:白马 == 传家宝,黑马 == 传家宝,传家宝 == 传家宝,白马 == 黑马;扯远了。

这条规则可以理解为大小关系,比如:a 大于 b,b 大于 c,那么 a 一定大于 c。

传递性规则可以与其他规则结合起来使用,达到意想不到的效果。

小结1

上述规则虽然只满足偏序关系,但同步操作,如锁的获取与释放等操作,以及 volatile 变量的读取与写入操作,都满足全序关系。因此,在描述 Happens-Before 关系时,可以使用 “后续的锁获取操作” 和 “后续的 volatile 变量的读取操作” 等表达术语。

final 域

在 Java 中,不管是在写类、写变量还是写方法时,大部分程序员可能都会忽视 final 关键字 的作用,从而导致一些不可预见的问题,在 Kotlin 中类和方法默认是 final 的,声明变量时建议使用 val 修饰而不是 var。

在 Java 中基于 final 关键字修饰的变量在多线程中也有不可变的特性,建议尽量优先考虑使用 final 来修饰变量。

总结

Java 内存模型就是对 JVM 的一种规范,其中 Happens-Before 定义了与程序员相关的一些规则,可以理解为 Happens-Before 规则是底层对程序员暴露的一些接口,程序员正确的使用这些接口可以编写出正确的代码。

Happens-Before 规则可以说是强制规定了代码的一些执行顺序,同时表达出 后续操作可以看见前面一个操作的结果

对 Java 内存模型的学习,有助于分析解决遇到的线程安全问题,理解线程安全问题出现的原因,以及重塑自己的编程思想。

笔者认为最重要的是重塑自己的编程思想,俗话说 “读书破万卷, 下笔如有神”,理解原理,掌握规则,重塑思想后,才能在编码时写出 “Bug”(狗头保命)。

Java 并发编程属实难,笔者水平有限,如有错误之处,还请不吝赐教。

说明与参考文献


  1. 《Java并发编程实战-第十六章》 ↩︎ ↩︎

  2. 偏序关系 π 是集合上的一种关系,具有反对称、自反和传递属性 ↩︎

  3. 显示锁与内置锁在加锁和解锁等操作上有相同的内存语义 ↩︎

  4. 原子变量与 volatile 变量在读/写操作上有相同的语义 ↩︎

  5. Java内存模型FAQ(十)volatile是干什么用的 ↩︎

  6. 深入理解Java内存模型(四)——volatile ↩︎

  7. 即 finalize() 方法 ↩︎

yakit 验证码识别 进行webfuzzer 在渗透测试的过程中,我们可能需要对管理员密码进行枚举,或者密码喷洒的时候,遇到验证码就很烦。有没有自动化识别验证码的办法呢?有的,兄弟,有的。验证码的识别率很低,captcha-killer-modified对算术验证码识别正确率非常低,还会识别成乱七八糟的结果,这就非常麻烦。因为你不知道到底是验证码不对,还是密码不对。这是 ddddocr 识别率的问题,如果师傅们找到准确度更高的工具,完全可以替换上去。 阅读详情

相关推荐

ZEMAX知识点:坐标间断面(coordinate break)

zemax知识点:坐标间断面

weixin_47638568的博客 1万+

JVM系列之:对象的锁状态和同步

文章目录简介java对象头java中锁状态的变化偏向锁biased locking轻量级锁thin lock重量级锁三种锁状态的不同 简介 锁和同步是java多线程编程中非常常见的使用场景。为了锁定多线程共享的对象,Java需要提供一定的机制来实现共享对象的锁定,从而保证一次只有一个线程能够作用于共享对象。当第二个线程进入同一个区域的时候,必须等待第一个线程解锁该对象。 JVM是怎么做到的呢?为了实现这个功能,java对象又需要具备什么样的结构呢?快来一起看看吧。 java对象头 Java的锁状态其实可以分

程序那些事 2万+

RDP Wrapper失效了?别慌!教你如何手动为Windows 10/11最新版本添加rdpwrap.ini配置

本文详细介绍了当RDP Wrapper在Windows 10/11最新版本中失效时,如何手动添加rdpwrap.ini配置以恢复多用户远程桌面功能。通过获取最新配置文件、安全替换配置的完整流程以及高级手动配置技巧,帮助用户解决[Not support]问题,确保远程工作流程不受系统更新影响。

weixin_30765577的博客 624

Java并发编程二三事

Java并发编程二三事 转自我的Github 近日重新翻了一下《Java Concurrency in Practice》故以此文记之。 我觉得Java的并发可以从下面三个点去理解: * 编译重排序 * 内存模型 * 资源竞争 这三个概念在Java并发中是非常重要的,最好也可以阅读相关资料理解一下。 一些概念 Amdahl定律 在包含N个处理器的机器中,最高的加...

weixin_30919235的博客 148

uniapp开发AndroidAPP集成支付宝支付及问题处理(商家订单参数异常等),集成微信小程序支付及问题处理(运行真机调试白屏)

2、选择支付宝支付,调用支付接口唤起支付宝支付。

qq_16785561的博客 3486

nrf52840的uart0与uart1双串口使用

nrf52840的uart0与uart1双串口使用 项目原使用nrf52832,后面由于项目需要用到双串口,改用nrf52840,官方的例程没看到使用uart1的,找遍全网都没找到uart1的使用资料,有些不负责任的网文居然说40也只有一个uart,吓得俺赶紧又查看了一次datesheet,O得K、木鸭梨 SDK代码uart驱动中,搜索到uart1的驱动部分,说明官方驱动有uart1支持的 自己动手,丰衣足食吧,下面以nRF5_SDK_17.0.0_9d13099版本,在examples\ble_per

chhen168的博客 5625

JMM(Java内存模型)详解

1.什么是JMM? ​ JMM 是Java内存模型( Java Memory Model),简称JMM。它本身只是一个抽象的概念,并不真实存在,它描述的是一种规则或规范。通过这组规范,定义了程序中对各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。 ​ 计算机在执行程序时,每条指令都是在CPU中执行的。而执行指令的过程中,势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过

LYQ20010417的博客 3万+

FPGA学习之路—应用程序—原码二位乘法器及Verilog代码分析

FPGA学习之路——原码二位乘法器及Verilog代码分析 原理 原码乘法可以分为原码一位乘和原码二位乘,两者在实现规则上大同小异。原码一位乘每次判断乘数的最低位,对被乘数和部分积进行相应操作。而原码二位乘则是对乘数的低二位进行判断,并执行相关操作。 两位乘数的取值可以有四种可能组合,每种组合对应于以下操作: 00相当于0x,部分积Dresult右移2位,不进行其他运算; 01相当于1x,部分积D...

谦豫 3716

JMM概述

简介什么是JMM内存模型可以理解为在特定的操作协议下,对特定的内存或者高速缓存进行读写访问的过程抽象描述,不同架构下的物理机拥有不一样的内存模型,Java虚拟机是一个实现了跨平台的虚拟系统,因此它也有自己的内存模型,即Java内存模型(Java Memory Model, JMM)。因此它不是对物理内存的规范,而是在虚拟机基础上进行的规范从而实现平台一致性,以达到Java程序能够“一次编写,到处运行

牧竹子 6万+

rk3588s-摄像头适配gc8034

若还打开失败,需要看下XML中文件配置是否与DTS配置信息一致,或者XML参数配置是否有异常;MIPI摄像头是有上电时序要求,具体时序要查看sensor芯片对应的datasheet进行修改,如果时序不满足,sensor无法正常,此时用i2ctool去查看设备可能无法扫面到设备,即是i2c设备找不到。RK3588或者RK3588S有路MIPI-PHY,驱动开发工程师需要根据对应的硬件原图进行配置,以下会讲解分别使用MIPI-DPHY和MIPI-D/CPHY如何在DTS配置mipi摄像头的回环链路。

Yxx8066的博客 2200

使用 Simulink + Simscape Electrical 实现一个变压器耦合式单相全波整流器的仿真模型

本文介绍了基于Simulink的变压器耦合式单相全波整流器建模与仿真方法。详细说明了从项目创建、模块添加、电路搭建到参数配置的完整建模过程,重点分析了整流器的效率与损耗特性,包括变压器损耗和二极管导通损耗。通过仿真展示了输入输出波形特征,并提出了选用高效元件、优化变压器设计等改进建议。该案例为电力电子系统建模提供了实用参考,有助于深入理解整流电路工作原理和性能优化方法。

amy_mhd的博客 187

JMM(Java 内存模型)详解

所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量)每个线程都有一个私有的本地内存来存储共享变量的副本,并且,每个线程只能访问自己的本地内存,无法访问其他线程的本地内存。本地内存是 JMM 抽象出来的一个概念,存储了主内存中的共享变量副本。线程 A 把本地内存中修改过的共享变量副本的值同步到主内存中去。线程 B到主存中读取对应的共享变量的值。也就是说,JMM 为共享变量提供了可见性的保障。

m0_51431003的博客 7334

4.26华为OD机试真题 新系统 - 端口流量统计 (Java/Py/C/C++/Js/Go)

本文整理华为 OD 机试新系统机考真题库,涵盖高频考点精析与完整题目内容,提供 C 语言、Java、C++、Python、Go、JavaScript 多语言源码实现,搭配详细解题思路讲解,并支持在线 OJ 刷题练习,助力高效备考华为 OD 机试新系统。

算法大师! 2334

【JUC并发编程】Java内存模型——JMM

【JUC并发编程】Java内存模型——JMM

blblccc 840

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

JUC并发编程—— JMM (Java内存模型)

文章目录JUC并发编程—— JMM (Java内存模型)1、JMM 概述2、JMM 三个特性 JUC并发编程—— JMM (Java内存模型) 1、JMM 概述 什么是 JMM Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。 内存划分 JMM规定了内存主要划分为主内存和工作内存两种。 Java内存模型规定了所有的变量都存储在主内存中,每条线程还有

万里顾一程的博客 576

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

【Java内存模型】Java内存模型(JMM)详解以及并发编程的三个重要特性(原子性,可见性,有序性)

目录 1 Java内存模型(Java memory model JMM) 1.1 什么是JMM(JMM的作用) 1.2 JMM的组成 2 硬件内存架构与java内存模型 2.1 硬件架构 2.2 Java线程与硬件处理器 2.3 Java内存模型与硬件内存架构的关系 2.4 Java内存模型的必要性 3.并发编程的三个重要特性 3.1 原子性 3.2 可见性 3.3...

小七的博客 5748

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

java并发编程-JMM内存模型

原子性、可见性、有序性。

zhujilisa的博客 457

JUC并发编程 04——Java内存模型之JMM

JUC并发编程 04——Java内存模型之JMM

qq_62767608的博客 1307

深入理解Java内存模型(JMM)及其在并发编程中的应用

JMM定义了线程和主内存之间的抽象关系,决定了变量读取和写入的内存可见性,以及并发线程之间的原子性和有序性问题。Java内存模型是Java虚拟机(JVM)规范中定义的一种内存模型,它描述了Java程序中各种变量(包括实例字段、静态字段和构成数组对象的元素)的访问规则,以及在多线程环境中线程之间如何共享和同步这些变量的内存可见性语义。JMM通过主内存与工作内存之间的交互,以及Happens-Before规则,保证了线程对共享变量的修改能够及时被其他线程看到,从而实现了内存可见性。二、Java内存模型概述。

铭渊老黄 930

【深入浅出】揭秘Java内存模型(JMM):并发编程的基石

Java内存模型(JMM)是理解并发编程的核心,它定义了多线程环境下共享变量的访问规则。JMM通过主内存和工作内存的抽象概念,解决可见性、原子性和有序性三大并发难题。关键机制包括Happens-Before原则和内存屏障,volatile和synchronized等关键字正是基于这些原理实现线程安全。深入理解JMM能帮助开发者编写正确高效的多线程代码,并掌握并发工具类的底层原理。

houzhangbo的专栏 1061

JUC-并发编程-02-java内存模型JMM

最近思考了一下,要学习并发编程,应该先把JMM的知识点熟悉,后面才好学习。话不多说步入正题。、 1、概述: Java线程之间的通信对程序员完全透明,内存可见性问题很容易困扰Java程序员,本章将揭开Java内存模型神秘的面纱。本章大致分4部分:Java内存模型的基础,主要介绍内存模型相关的基本概念;Java内存模型中的顺序一致性,主要介绍重排序与顺序一致性内存模型;同步原语,主要介绍3个同步原语(synchronized、volatile和final)的内存语义及重排序规则在处理器中的实现;Java内存

qq_29434541的博客 451

深入解析Java内存模型(JMM)从并发编程的基石到实战应用

Java内存模型(JMM)是Java并发编程的核心理论基础,它抽象了线程与主内存之间的关系,定义了共享内存系统中多线程程序读写操作行为的规范。通过理解JMM的核心概念,如主内存与工作内存的交互、重排序、Happens-Before规则,以及volatile、synchronized和final等关键字的内存语义,开发者能够洞察并发程序的内在运行机制,从而编写出线程安全、高效可靠的代码。在实战中,结合JMM的原理来选择和设计同步策略,是解决并发问题的根本之道。

SenJune的博客 833

深入理解Java内存模型(JMM)——让并发编程更轻松

Java内存模型(JMM)定义了Java程序中各个线程对内存的访问规则。它不仅规定了不同线程如何共享内存,也明确了多线程间的通信机制。通过JMM,Java虚拟机(JVM)保证了多线程环境下的程序一致性,同时尽可能提高程序的执行效率。在Java中,每个线程都有自己的本地内存(也称工作内存),而共享变量则存储在主内存中。线程通过工作内存和主内存之间的交互来实现数据的共享。JMM规定了对共享变量的访问如何同步,保证了多线程之间的正确性和一致性。Java内存模型(JMM)是并发编程中不可忽视的核心概念。

一碗黄焖鸡三碗米饭的博客 1039

面试官:讲一讲 Java 内存模型(JMM)与并发编程实践?

【摘要】本文介绍了Java内存模型(JMM)的核心概念及其在并发编程中的应用。JMM解决了多线程环境下的变量可见性、原子性和有序性问题,采用主内存与工作内存机制实现线程间通信。重点分析了并发三大特性(原子性、可见性、有序性)和happens-before原则,对比了volatile与synchronized的区别,并针对双重检查锁等典型场景给出正确实现方案。文章最后总结了并发编程实践建议和常见问题,为Java开发者提供了应对并发挑战的系统性指导。

真正的大师永远怀着一颗学徒的心 955

JAVA并发编程之JAVA内存模型(JMM)

JAVA并发编程之JAVA内存模型(JMM) 相关概念 进程和线程的区别与联系 进程是资源分配的最小单位,线程是程序执行的最小单位,服务器上的每一个应用都是一个进程,例如我们电脑的应用程序。 进程与进程间的通信 管道pipe 命名管道FIFO 消息队列MessageQueue 共享存储SharedMemory 信号量Semaphore 套接字Socket 信号 ( sinal ) JAVA线程之间的通信 synchronized同步 while轮询 wait/notify机制 管道通信 JAVA内存

gcxxr的博客 212

深入解析Java内存模型(JMM)并发编程的核心基石与实战指南

深入理解JMM是编写正确、高效Java并发程序的关键。总结其核心要点与实践建议如下:1.优先使用高层抽象:尽量使用`java.util.concurrent`包下的并发容器和工具类,而非自己实现复杂的同步逻辑。2.明确同步需求:如果只是简单的状态标志,使用`volatile`。如果需要保证复合操作的原子性,使用`synchronized`或`Lock`。3.减小同步范围:在使用锁时,尽量缩小同步代码块的范围,以提高程序的并发性能。4.理解Happens-Before。

builder_ming的博客 749
上一篇: Kotlin-KCP的应用-修改SDK版本号
guodongAndroid
博客等级 码龄10年 16粉丝 19原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值