【Java技术专题】「入门到精通系列教程」深入探索Java特性中并发编程体系的原理和实战开发指南(内存模型技术专题)

深入探索Java特性中并发编程体系的原理和实战开发指南( 线程进阶技术专题)

  • 前言介绍
  • JVM内存模型
  • 运行时数据区域
    • 堆内存
    • 栈内存
  • 内存访问规则
    • 原子性
      • 对象类型
      • 基本类型
    • 可见性
    • 有序性(Happen Before法则)
    • 系统内存(MESI协议)
  • 内存栅栏
    • 查看JIT编译结果
      • 这行配置的含义如下
    • 缓存行对齐
      • **缓存行对齐**
      • **伪共享**
  • 线程安全策略
    • 不可变类
    • 线程栈内使用
    • 同步锁
    • CAS (CompreAndSet)
  • 编程建议指南

前言介绍

JVM内存模型是Java程序实现线程安全和并发性的重要基础,对于Java程序员来说必须深入理解其中的原理和细节,才能有效避免由JVM内部的并发问题导致的程序错误和性能问题。

JVM内存模型

JVM内存模型是Java虚拟机规范中定义的一种用于管理内存使用的模型,主要分为两个部分:

  • 运行时数据区域:是Java程序运行时需要使用的内存区域,包括堆内存、栈内存、本地方法栈、方法区和程序计数器等。

  • 内存访问规则:是Java程序中多线程访问共享数据时所需要遵守的规则,包括原子性、可见性和有序性。其中:
    在这里插入图片描述

运行时数据区域

堆内存

在Java中,所有的对象实例的属性都存储在共享堆内存空间中。这个空间被单字节对齐,保证了内存的高效使用。需要注意的是,short类型的属性在堆内存中是无法被改变的。

栈内存

每个线程都有自己独立的线程栈空间,线程栈只存储基本数据类型和对象的地址。线程栈内存是4字节对齐且short型会被转化为int型。对象的地址长度为4字节且存储在引用堆空间中。方法内的局部变量存储在线程栈空间中,不会发生竞争,因此是线程安全的。方法的参数在栈顶交错存储,而不是被拷贝到栈顶寄存器中,这减少了中间状态的读取,同时也可以记录当前执行位置的PC指针。

内存访问规则

在这里插入图片描述

原子性

对象类型

  • 对象地址的原子读写是线程安全的: 在Java中,对象地址的读写是原子性的,并且具有线程安全性。这意味着多个线程可以同时读取或写入对象的地址,而不会导致数据竞争或内存不一致问题。

  • 对于不可变状态的并发读取是线程安全的: 如果对象在运行时保持其状态不变,那么多个线程同时读取它的状态是安全的。这个时候,读取操作之间没有任何干扰和依赖关系,并且可以自由地共享访问。因此,这种情况下是线程安全的。

  • 对于可变状态的并发读写不是线程安全的: 如果一个对象在运行时可以被多个线程同时读取和写入,那么在并发访问的情况下,就会发生数据竞争和内存不一致问题。这时候需要采取线程同步的措施来保证线程安全。例如,可以使用synchronized或Lock等机制来确保同时只有一个线程在执行对对象的读取或写入操作。

基本类型

  • 对于int和char类型的数值读写是线程安全的: 在Java中,int和char数据类型的读写操作是原子性的,因此具有线程安全性。这意味着多个线程可以同时读取或写入int和char类型的变量,而不会导致数据竞争或内存不一致的问题。

  • 对于long和double类型的高低位读写并不是线程安全的: 在Java中,long和double数据类型的高低位读写是非原子性的,这意味着在多个线程同时对一个long或double进行读写时,可能导致数据竞争和内存不一致的问题。为了确保线程安全性,需要采用同步机制来解决这个问题。

  • i++等组合操作不是线程安全的: i++等组合操作包含读写两个操作,并且具有非原子性,因此在多线程并发执行时是非线程安全的。这意味着在多个线程对同一个变量执行i++等组合操作时,可能导致数据竞争和内存不一致的问题。为了确保线程安全性,需要采用同步机制来解决这个问题。例如,可以使用synchronized或AtomicInteger等线程安全类来进行同步控制。

可见性

下面是对你提供的内容的润色和优化:

  • final关键字可以确保final字段的可见性: 当一个final字段被初始化后,其值不能再被修改,这意味着在多个线程间访问final字段时不会出现内存不一致的问题。同时,在Java语言规范中,final字段的初始化具有内存屏障的作用,确保了final字段初始化后对其他线程的可见性。

  • volatile关键字可以确保volatile字段的可见性: 通过使用volatile关键字声明的变量,每次读写时都会对内存进行同步操作,确保了变量的可见性。这意味着当一个线程修改了volatile变量的值后,其他线程能够立即看到该变量新的值,而不会看到可见性问题引起的数据不一致。

  • synchronized关键字可以确保同步块内读写字段的可见性: 在一个synchronized块中,当一个线程对某个字段进行写操作时,会立即将其刷新到主存储器中,同时其他线程在进入该synchronized块时,会首先尝试从主存储器中获取最新的字段值,从而确保了字段的可见性和一致性。

  • happen-before规则可以确保遵守happen-before次序的可见性: happen-before规则是Java内存模型中的一组规则,可以确保多个线程间的内存可见性。其中,如果一个操作happen-before另一个操作,那么前一个操作的结果对于后一个操作来说是可见的。在Java中,例如synchronized同步块、volatile变量的读写、启动线程和join线程等操作都是基于happen-before规则的,可以确保多个线程之间的内存可见性。

有序性(Happen Before法则)

  1. 程序次序法则: 如果A发生在B之前,则A和B之间具有happen before关系。

  2. 监视器法则: 监视器的解锁一定发生在后续对同一监视器加锁之前。

  3. Volatile变量法则: 写入volatile变量一定发生在后续对它的读取之前。

  4. 线程启动法则: 线程中的所有动作一定发生在Thread.start方法之前。

  5. 线程终结法则: 其他线程检测到某一线程已经终止,从Thread.join调用成功返回,或Thread.isAlive()返回false的发生一定在该线程中的所有动作之前。

  6. 中断法则: 一个线程调用另一个线程的interrupt方法一定发生在另一个线程检测到中断之前。

  7. 终结法则: 一个对象的构造函数结束一定发生在对象的finalizer方法之前。

  8. 传递性法则: 如果A发生在B之前,B发生在C之前,那么A一定发生在C之前。

系统内存(MESI协议)

Modified
本CPU写,则直接写到Cache,不产生总线事务;其它CPU写,则不涉及本CPU的Cache,其它CPU读,则本CPU需要把Cache line中的数据提供给它,而不是让它去读内存。
Exclusive
只有本CPU有该内存的Cache,而且和内存一致。 本CPU的写操作会导致转到Modified状态。
Shared
多个CPU都对该内存有Cache,而且内容一致。任何一个CPU写自己的这个Cache都必须通知其它的CPU。
Invalid
一旦Cache line进入这个状态,CPU读数据就必须发出总线事务,从内存读。

在这里插入图片描述

内存栅栏

volatile int a, b; if(a == 1 && b == 2)

JIT通过load acquire依赖保证读顺序:

0x2000000001de819c:  adds r37=597,r36;;  ;...84112554
0x2000000001de81a0:  ld1.acq r38=[r37];;  ;...0b30014a a010

volatile A a; a = new A();

JIT通过lock addl使CPU的cache line失效:

0x01a3de1d: movb $0x0,0x1104800(%esi);
0x01a3de24: lock addl $0x0,(%esp);

查看JIT编译结果

java -XX:+UnlockDiagnosticVMOptions -XX:PrintAssemblyOptions=hsdis-print-bytes -XX:CompileCommand=print,*AtomicInteger.incrementAndGet

这行配置的含义如下

  • java:代表要启动 Java 虚拟机以执行 Java 代码。
  • -XX:+UnlockDiagnosticVMOptions:打开 JVM 的诊断选项,该选项允许开发人员使用需要特权的命令。
  • -XX:PrintAssemblyOptions=hsdis-print-bytes:使用 HSDis(HotSpot Disassembler)打印本机代码的字节表示。这可以用于调试,分析代码优化和性能问题。
  • -XX:CompileCommand=print,*AtomicInteger.incrementAndGet:当使用 JIT 编译器编译调用 AtomicInteger.incrementAndGet() 方法的代码时,打印代码的汇编输出。

综上所述,这条配置命令允许开发人员在 JVM 中启用诊断选项并使用 HotSpot Disassembler 打印 Java 代码编译成的机器码汇编输出。其中,通过 -XX:CompileCommand=print,*AtomicInteger.incrementAndGet 捕捉了 AtomicInteger.incrementAndGet() 这个方法的编译过程,可以分析该方法对应的本地代码的汇编输出,这对于调试和分析性能问题非常有用。

缓存行对齐

缓存行对齐和伪共享都是与CPU缓存有关的概念。缓存是小型且从主内存中读取和写入数据比内存操作更快的内存。缓存行大小通常是64字节。当多个线程或处理器核心在操作共享变量时,缓存就会成为一个问题。

缓存行对齐

每个缓存行保存着多个数据元素。如果两个数据元素在同一个缓存行中,它们会在同一时刻被加载到CPU缓存中,这样就可以提高程序的性能。因此,缓存行对齐是在我们能够控制的数据元素之间添加填充以使它们位于不同的缓存行中。对于Java,可以使用 sun.misc.Contended 注解来实现线程对齐。

LinkedTransferQueue
static final class PaddedAtomicReference <T> extends AtomicReference <T> {
    Object p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, pa, pb, pc, pd, pe;
    PaddedAtomicReference(T r) {
        super(r);
    }
}

16个地址的长度,刚好占满一个cache line的长度。确保两个引用,不在同一cache line上,防止多锁竞争。

伪共享

当两个线程完全独立但共享同一个缓存行中的不同数据元素时,可能会发生伪共享。操作其中一个数据元素会导致整个缓存行从内存中加载到CPU缓存,这会导致另一个线程无法访问该缓存行。这种现象称为“伪共享”,它会导致性能下降。

为了解决伪共享问题,可以使用一些技术来提高线程之间的独立性,例如将共享的数据分离到单独的缓存行中或者使用volatile变量或者Atomic方式。此外,可以使用一些特殊的注解来告诉编译器和运行时环境我们希望它们处理伪共享,例如Java 8开始引入的 sun.misc.Contended 注解。

线程安全策略

不可变类

如果一个类初始化后,所有属性和类都是final不可变的,这确实可以增加线程安全性,可以避免一些并发问题,例如多个线程同时修改同一对象可能出现的数据不一致等。

然而,即使这个类没有显式的同步,也不能保证它的线程安全,因为final关键字只能保证对对象的引用不变,而不能保证对象本身的线程安全性。如果这个类的方法没有进行同步,需要访问的对象的状态可能会发生变化,从而导致并发问题,例如读取脏数据、重复数据、遗漏数据等。

因此,即使一个类所有属性和类都是final不可变的,也不能保证这个类的线程安全,需要视具体情况进行合适的同步或其他线程安全措施。只有在确保访问对象的方法也是线程安全的情况下,才能认为这个类是完全线程安全的。

线程栈内使用

涉及多线程应用程序时,有几种方法可以提高应用程序的性能和可维护性,分别是方法内局部变量使用、线程内参数传递和使用ThreadLocal持有变量。

  1. 方法内局部变量使用

对于方法中仅在方法内被使用的变量,应该将其声明为局部变量,而不是作为全局变量存储在堆上。这样可以减少对象的创建数量,从而降低垃圾回收的负载,提高代码执行效率。

  1. 线程内参数传递

在多线程应用程序中,当一个方法需要访问某个对象时,最好将对象引用传递到方法中,而不是将对象作为全局变量。这样可以避免多个线程同时修改同一个对象的情况,从而提高应用程序的线程安全性。

  1. ThreadLocal持有变量

在线程之间传递数据时,如果不希望将数据暴露给其他线程,可以将数据存储在ThreadLocal对象中。ThreadLocal是Java中的一种线程范围内的数据结构,可以用于在不同的线程中存储和获取对象的值,而不必担心多个线程之间干扰。

综上所述,方法内局部变量的使用、线程内参数传递和使用ThreadLocal都是优化多线程应用程序的有效方法。在编写代码时,应综合考虑以上因素,以提高代码的执行效率和可维护性。

同步锁

  • 使用synchronized关键字锁定的代码会保证在同一时刻只有一个线程可以访问共享资源,因此它具有较高的线程安全性。但是,由于每个线程都必须等待前一个线程完成它的工作后才能继续执行,因此活性较低。
  • volatile变量可以确保可见性和禁止重排序,但只能在一些有限的情况下使用。可以使用锁外双重检测来尽可能地减少锁竞争,提高程序的性能。需要注意的是,对于访问次数较少的变量,使用volatile变量作为同步锁可能会影响程序的性能,因为锁的开销相对较大。
  • 读写条件分离、锁粒度分级和排序锁等技术可以降低锁的竞争,提高程序的性能。读写条件分离指的是将对共享资源的读和写操作分别加锁,从而允许多个线程并发地进行读操作。锁粒度分级指的是根据数据结构的特点,将锁的粒度分为不同的级别,避免过度细粒度的锁导致的锁竞争。排序锁则是对锁进行排序,以避免死锁和饥饿问题。这些技术需要根据具体情况进行灵活应用,以达到最优的性能和线程安全性的平衡。

CAS (CompreAndSet)

这里描述了一种基于“冲突检测与重试”的乐观并发方案。在这种方案中,每次更新操作时,使用比较并交换(CAS)指令判断当前值是否与期望值相等,若相等,则更新为新值;否则,表示中途有其他线程修改过该值,需要重新读取值并重试操作。

这种方案的优点是在没有竞争的情况下,可以快速地进行操作,提高程序的性能,并且不会发生死锁和饥饿问题。缺点是在竞争情况下,需要频繁地进行重试操作,消耗较多的CPU资源,并且可能导致进程的长时间阻塞,因此需要根据具体应用情况进行评估。

需要注意的是,乐观并发方案适用于不需要特别强的一致性要求,且数据冲突发生的概率较低的场景,例如计数器等任务。如果数据冲突较为频繁,建议采用悲观并发方案(例如使用锁进行同步),以保证数据的安全性和一致性。

编程建议指南

在编写代码时,需要考虑以下问题:

  1. 敲每个点号时,是否会出现空指针异常?

  2. 是否会有异常抛出?

  3. 代码是否在热点区域?

  4. 代码是在哪个线程执行?

  5. 是否存在并发锁的间隙?

  6. 是否会并发修改不可见?

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

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

相关文章

群体遗传 — 核苷酸多样性π

群体遗传 — 核苷酸多样性π **核苷酸多样性&#xff08;nucleotide diversity&#xff09;&#xff0c;记为π&#xff0c;是分子遗传学中一个重要的概念&#xff0c;用于量化种群内部或不同种群间的遗传多样性。**这一概念由根井正利和李文雄在 1979 年提出。核苷酸多样性的…

mysql原理--InnoDB记录结构

1.InnoDB行格式 我们平时是以记录为单位来向表中插入数据的&#xff0c;这些记录在磁盘上的存放方式也被称为 行格式 或者 记录格式 。 设计 InnoDB 存储引擎的大叔们到现在为止设计了4种不同类型的 行格式 &#xff0c;分别是 Compact 、 Redundant 、Dynamic 和 Compressed 行…

随时随地查看远程试验数据与记录——IPEhub2与IPEmotion APP

一 背景 在工况恶劣、空间狭小的试验场景或工程机械领域中&#xff0c;不但试验人员在试验环境中对自身安全没有保障&#xff0c;而且试验过程也会受到影响&#xff0c;如高温高压测试、工程机械液压系统测试等。对此&#xff0c;结合IPEhub2与IPEmotion APP&#xff0c;既可保…

「GitHub资源」DevToys开发者神器,堪称程序员界的瑞士军刀!

如果你是一个 Windows 开发者&#xff0c;你是否经常需要在网上搜索一些工具来完成一些简单的任务&#xff0c;比如格式化 JSON&#xff0c;比较文本&#xff0c;测试正则表达式&#xff0c;转换数据类型&#xff0c;生成二维码&#xff0c;编码解码字符串等等&#xff1f;你是…

无脑018——win11部署whisper,语音转文字

1.conda创建环境 conda create -n whisper python3.9 conda activate whisper安装pytorch pip install torch1.8.1cu101 torchvision0.9.1cu101 torchaudio0.8.1 -f https://download.pytorch.org/whl/torch_stable.html安装whisper pip install -U openai-whisper2.准备模型…

Mysql索引案例分析

这篇文章写个案例&#xff0c;测试一下MySQL索引机制 测试表结构 CREATE TABLE t_qrcode_op (id int(11) NOT NULL AUTO_INCREMENT COMMENT 主键,op_mobile varchar(16) NOT NULL,pr_code char(10) NOT NULL,PRIMARY KEY (id),UNIQUE KEY om_pc (op_mobile,pr_code) USING BTR…

SSH原理与应用与瞎玩

Secure Shell(SSH 安全外壳协议) 是由 IETF(The Internet Engineering Task Force) 制定的建立在应用层基础上的安全网络协议。它是专为远程登录会话(甚至可以用Windows远程登录Linux服务器进行文件互传)和其他网络服务提供安全性的协议&#xff0c;可有效弥补网络中的漏洞。通…

前后端分离vue+Nodejs社区志愿者招募管理系统

1、首页 1)滑动的社区照片册 使用轮播图&#xff0c;对社区的活动纪念与实时事件宣传。 每个图片附有文字链接&#xff0c;点击跳转对应社区要闻具体页。 2)社区公告栏 日常的社区公告以及系统说明在此区域中进行说明与展示。 2、志愿活动 1)志愿活动发布 想发布需要登录 2)志愿…

html和css写QQ会员页面导航

目录 1、css代码 2、html代码 效果图 1、css代码 <style>* {padding: 0;margin: 0;list-style: none;text-decoration: none;}div {margin: 30px auto;}li {float: left;height: 60px;background-color: rgb(102, 102, 102);line-height: 40px;}img {height: 100%;ma…

Hadoop学习笔记(HDP)-Part.09 安装OpenLDAP

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

索尼PMW580视频帧EC碎片重组开启方法

索尼PMW580视频帧EC碎片重组开启方法 索尼PMW-580摄像机生成的MXF文件存在严重的碎片化&#xff0c;目前CHS零壹视频恢复程序MXF版、专业版、高级版已经支持重组结构体正常的碎片&#xff0c;同时也支持对于结构体破坏或者覆盖后仅存在音视频帧EC数据的重组&#xff0c;需要注…

论文阅读:一种通过降低噪声和增强判别信息实现细粒度分类的视觉转换器

论文标题&#xff1a; A vision transformer for fine-grained classification by reducing noise and enhancing discriminative information 翻译&#xff1a; 一种通过降低噪声和增强判别信息实现细粒度分类的视觉转换器 摘要 最近&#xff0c;已经提出了几种基于Vision T…

【数据结构—单链表的实现】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 1. 链表的概念及结构 2. 单链表的实现 2.1单链表头文件——功能函数的定义 2.2单链表源文件——功能函数的实现 2.3 单链表源文件——功能的测试 3.具体的理解操作…

ES-环境安装(elasticsearch:7.17.9,kibana,elasticsearch-head)

ES 环境搭建 1 拉取镜像 常用三件套 docker pull kibana:7.17.9 docker pull elasticsearch:7.17.9 docker pull mobz/elasticsearch-head:52 启动镜像 elasticsearch 安装 这里可以先不挂载文件启动一波&#xff0c;然后把容器里的文件拷贝出来 docker run -p 19200:9200 …

【Linux系统编程】开发工具yum和vim

目录 一&#xff0c;yum工具的使用 1&#xff0c;yum的介绍 2&#xff0c;yum的使用 二&#xff0c;vim工具的开发 1&#xff0c;vim的介绍 2&#xff0c;模式的使用 3&#xff0c;vim配置文件 4&#xff0c;sudo配置文件 一&#xff0c;yum工具的使用 1&#xff0c;y…

2023美图创造力大会开幕,美图发布AI视觉大模型4.0

12月5-6日&#xff0c;主题为“未来AI设计”的美图创造力大会&#xff08;Meitu Creativity Conference&#xff0c;简称MCC&#xff09;在厦门举行。 本届大会由美图公司与站酷联合举办&#xff0c;聚焦于设计师生态和AI设计趋势。大会现场发布《2023年度AI设计实践报告》&am…

WeiPHP 微信开发平台 SQL注入漏洞复现

0x01 产品简介 weiphp 是一个开源,高效,简洁的微信开发平台,基于 oneThink 内容管理框架实现。 0x02 漏洞概述 weiphp 微信开发平台 _send_by_group、 wp_where、 get_package_template等接口处存在 SQL 注入漏洞,攻击者利用此漏洞可获取数据库中的信息(例如,管理员后台…

中标!世界500强中信集团携手道本科技共建风险管理应用三期建设项目

近日&#xff0c;天津市道本科技有限公司&#xff08;以下简称“道本科技”&#xff09;中标世界500强中国中信集团有限公司&#xff08;以下简称“中信集团”&#xff09;风险管理应用三期建设项目。 作为金融与实业并举的综合性跨国企业集团&#xff0c;中信集团已连续12年入…

虾皮在线定价工具:知虾轻松制定有竞争力的价格策略

在如今的电商市场中&#xff0c;如何设定合适的商品价格是卖家们面临的一个重要问题。为了帮助卖家解决这个难题&#xff0c;虾皮&#xff08;Shopee&#xff09;提供了一款在线定价工具。通过这个工具&#xff0c;您可以更轻松地为您的商品制定有竞争力的价格策略&#xff0c;…

pytest接口自动化测试框架搭建的全过程

一. 背景 Pytest目前已经成为Python系自动化测试必学必备的一个框架&#xff0c;网上也有很多的文章讲述相关的知识。最近自己也抽时间梳理了一份pytest接口自动化测试框架&#xff0c;因此准备写文章记录一下&#xff0c;做到尽量简单通俗易懂&#xff0c;当然前提是基本的py…