深入理解java内存模型

深入理解 Java 内存模型 文章目录深入理解 Java 内存模型(一)——基础并发编程模型的分类Java 内存模型的抽象重排序处理器重排序与内存屏障指令happens-before深入理解 Java 内存模型(二)——重排序数据依赖性as-if-serial 语义程序顺序规则重排序对多线程的影响深入理解 Java 内存模型(三)——顺序一致性数据竞争与顺序一致性保证顺序一致性内存模型同步程序的顺序一致性效果未同步程序的执行特性深入理解 Java 内存模型(四)——volatilevolatile 的特性volatile 写 - 读建立 阅读详情

最近看到一篇比较好的介绍java内存模型的文章,特记录下来

原文参考:https://juejin.im/post/5bf2977751882505d840321d

下文据原作者是看了《深入学习java虚拟机》总结出来的,博主也得找个时间去看看这本优秀的书才行了

一,内存模型产生背景

       在介绍Java内存模型之前,我们先了解一下物理计算机中的并发问题,理解这些问题可以搞清楚内存模型产生的背景。物理机遇到的并发问题与虚拟机中的情况有不少相似之处,物理机的解决方案对虚拟机的实现有相当的参考意义。

物理机的并发问题

  • 硬件的效率问题

计算机处理器处理绝大多数运行任务都不可能只靠处理器“计算”就能完成,处理器至少需要与内存交互,如读取运算数据、存储运算结果,这个I/O操作很难消除(无法仅靠寄存器完成所有运算任务)。

由于计算机的存储设备与处理器的运算速度有几个数量级的差距,为了避免处理器等待缓慢的内存读写操作完成,现代计算机系统通过加入一层读写速度尽可能接近处理器运算速度的高速缓存。缓存作为内存和处理器之间的缓冲:将运算需要使用到的数据复制到缓存中,让运算能快速运行,当运算结束后再从缓存同步回内存之中。 

 

  • 缓存一致性问题

基于高速缓存的存储系统交互很好地解决了处理器与内存速度的矛盾,但是也为计算机系统带来更高的复杂度,因为引入了一个新问题:缓存一致性。

在多处理器的系统中(或者单处理器多核的系统),每个处理器(每个核)都有自己的高速缓存,而它们有共享同一主内存(Main Memory)。当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致。 为此,需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议进行操作,来维护缓存的一致性。

  • 代码乱序执行优化问题

为了使得处理器内部的运算单元尽量被充分利用,提高运算效率,处理器可能会对输入的代码进行乱序执行,处理器会在计算之后将乱序执行的结果重组,乱序优化可以保证在单线程下该执行结果与顺序执行的结果是一致的,但不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致。

乱序执行技术是处理器为提高运算速度而做出违背代码原有顺序的优化。在单核时代,处理器保证做出的优化不会导致执行结果远离预期目标,但在多核环境下却并非如此。

多核环境下, 如果存在一个核的计算任务依赖另一个核 计的算任务的中间结果,而且对相关数据读写没做任何防护措施,那么其顺序性并不能靠代码的先后顺序来保证,处理器最终得出的结果和我们逻辑得到的结果可能会大不相同。

以上图为例进行说明:CPU的core2中的逻辑B依赖core1中的逻辑A先执行

  • 正常情况下,逻辑A执行完之后再执行逻辑B。
  • 在处理器乱序执行优化情况下,有可能导致flag提前被设置为true,导致逻辑B先于逻辑A执行。

二, Java内存模型的组成分析

内存模型概念

为了更好解决上面提到系列问题,内存模型被总结提出,我们可以把内存模型理解为在特定操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。

不同架构的物理计算机可以有不一样的内存模型,Java虚拟机也有自己的内存模型。Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,简称JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果,不必因为不同平台上的物理机的内存模型的差异,对各平台定制化开发程序。

更具体一点说,Java内存模型提出目标在于,定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。此处的变量(Variables)与Java编程中所说的变量有所区别,它包括了实例字段、静态字段和构成数值对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的。(如果局部变量是一个reference类型,它引用的对象在Java堆中可被各个线程共享,但是reference本身在Java栈的局部变量表中,它是线程私有的)。

Java内存模型的组成

  • 主内存 Java内存模型规定了所有变量都存储在主内存(Main Memory)中(此处的主内存与介绍物理硬件的主内存名字一样,两者可以互相类比,但此处仅是虚拟机内存的一部分)。

  • 工作内存 每条线程都有自己的工作内存(Working Memory,又称本地内存,可与前面介绍的处理器高速缓存类比),线程的工作内存中保存了该线程使用到的变量的主内存中的共享变量的副本拷贝。工作内存是 JMM 的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

Java内存模型抽象示意图如下:

JVM内存操作的并发问题

结合前面介绍的物理机的处理器处理内存的问题,可以类比总结出JVM内存操作的问题,下面介绍的Java内存模型的执行处理将围绕解决这2个问题展开:

  • 1 工作内存数据一致性 各个线程操作数据时会保存使用到的主内存中的共享变量副本,当多个线程的运算任务都涉及同一个共享变量时,将导致各自的的共享变量副本不一致,如果真的发生这种情况,数据同步回主内存以谁的副本数据为准? Java内存模型主要通过一系列的数据同步协议、规则来保证数据的一致性,后面再详细介绍。

  • 2 指令重排序优化 Java中重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段。重排序分为两类:编译期重排序和运行期重排序,分别对应编译时和运行时环境。 同样的,指令重排序不是随意重排序,它需要满足以下两个条件:

    • 1 在单线程环境下不能改变程序运行的结果 即时编译器(和处理器)需要保证程序能够遵守 as-if-serial 属性。通俗地说,就是在单线程情况下,要给程序一个顺序执行的假象。即经过重排序的执行结果要与顺序执行的结果保持一致。
    • 2 存在数据依赖关系的不允许重排序

多线程环境下,如果线程处理逻辑之间存在依赖关系,有可能因为指令重排序导致运行结果与预期不同,后面再展开Java内存模型如何解决这种情况。

三,Java内存间的交互操作

在理解Java内存模型的系列协议、特殊规则之前,我们先理解Java中内存间的交互操作。

交互操作流程

为了更好理解内存的交互操作,以线程通信为例,我们看看具体如何进行线程间值的同步:

线程1和线程2都有主内存中共享变量x的副本,初始时,这3个内存中x的值都为0。线程1中更新x的值为1之后同步到线程2主要涉及2个步骤:

  • 1 线程1把线程工作内存中更新过的x的值刷新到主内存中
  • 2 线程2到主内存中读取线程1之前已更新过的x变量

从整体上看,这2个步骤是线程1在向线程2发消息,这个通信过程必须经过主内存。线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成,实现各个线程提供共享变量的可见性。

内存交互的基本操作

关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节,Java内存模型中定义了下面介绍8种操作来完成。

虚拟机实现时必须保证下面介绍的每种操作都是原子的,不可再分的(对于double和long型的变量来说,load、store、read、和write操作在某些平台上允许有例外,后面会介绍)。

8种基本操作

  • lock (锁定) 作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
  • unlock (解锁) 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read (读取) 作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
  • load (载入) 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use (使用) 作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值得字节码指令时就会执行这个操作。
  • assign (赋值) 作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store (存储) 作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后write操作使用。
  • write (写入) 作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

四,Java内存模型运行规则

4.1 内存交互基本操作的3个特性

在介绍内存的交互的具体的8种基本操作之前,有必要先介绍一下操作的3个特性,Java内存模型是围绕着在并发过程中如何处理这3个特性来建立的,这里先给出定义和基本实现的简单介绍,后面会逐步展开分析。

  • 原子性(Atomicity) 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。即使在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。

  • 可见性(Visibility) 是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。 正如上面“交互操作流程”中所说明的一样,JMM是通过在线程1变量工作内存修改后将新值同步回主内存,线程2在变量读取前从主内存刷新变量值,这种依赖主内存作为传递媒介的方式来实现可见性。

  • 有序性(Ordering) 有序性规则表现在以下两种场景: 线程内和线程间

    • 线程内 从某个线程的角度看方法的执行,指令会按照一种叫“串行”(as-if-serial)的方式执行,此种方式已经应用于顺序编程语言。
    • 线程间 这个线程“观察”到其他线程并发地执行非同步的代码时,由于指令重排序优化,任何代码都有可能交叉执行。唯一起作用的约束是:对于同步方法,同步块(synchronized关键字修饰)以及volatile字段的操作仍维持相对有序。

Java内存模型的一系列运行规则看起来有点繁琐,但总结起来,是围绕原子性、可见性、有序性特征建立。归根究底,是为实现共享变量的在多个线程的工作内存的数据一致性,多线程并发,指令重排序优化的环境中程序能如预期运行。

4.2 happens-before关系

介绍系列规则之前,首先了解一下happens-before关系:用于描述下2个操作的内存可见性:如果操作A happens-before 操作B,那么A的结果对B可见。happens-before关系的分析需要分为单线程和多线程的情况:

  • 单线程下的 happens-before 字节码的先后顺序天然包含happens-before关系:因为单线程内共享一份工作内存,不存在数据一致性的问题。 在程序控制流路径中靠前的字节码 happens-before 靠后的字节码,即靠前的字节码执行完之后操作结果对靠后的字节码可见。然而,这并不意味着前者一定在后者之前执行。实际上,如果后者不依赖前者的运行结果,那么它们可能会被重排序。

  • 多线程下的 happens-before 多线程由于每个线程有共享变量的副本,如果没有对共享变量做同步处理,线程1更新执行操作A共享变量的值之后,线程2开始执行操作B,此时操作A产生的结果对操作B不一定可见。

为了方便程序开发,Java内存模型实现了下述支持happens-before关系的操作:

  • 程序次序规则 一个线程内,按照代码顺序,书写在前面的操作 happens-before 书写在后面的操作。
  • 锁定规则 一个unLock操作 happens-before 后面对同一个锁的lock操作。
  • volatile变量规则 对一个变量的写操作 happens-before 后面对这个变量的读操作。
  • 传递规则 如果操作A happens-before 操作B,而操作B又 happens-before 操作C,则可以得出操作A happens-before 操作C。
  • 线程启动规则 Thread对象的start()方法 happens-before 此线程的每个一个动作。
  • 线程中断规则 对线程interrupt()方法的调用 happens-before 被中断线程的代码检测到中断事件的发生。
  • 线程终结规则 线程中所有的操作都 happens-before 线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行。
  • 对象终结规则 一个对象的初始化完成 happens-before 他的finalize()方法的开始

4.3 内存屏障

Java中如何保证底层操作的有序性和可见性?可以通过内存屏障。

内存屏障是被插入两个CPU指令之间的一种指令,用来禁止处理器指令发生重排序(像屏障一样),从而保障有序性的。另外,为了达到屏障的效果,它也会使处理器写入、读取值之前,将主内存的值写入高速缓存,清空无效队列,从而保障可见性。

举个例子:

Store1; 
Store2;   
Load1;   
StoreLoad;  //内存屏障
Store3;   
Load2;   
Load3;

对于上面的一组CPU指令(Store表示写入指令,Load表示读取指令),StoreLoad屏障之前的Store指令无法与StoreLoad屏障之后的Load指令进行交换位置,即重排序。但是StoreLoad屏障之前和之后的指令是可以互换位置的,即Store1可以和Store2互换,Load2可以和Load3互换。

常见有4种屏障

  • LoadLoad屏障: 对于这样的语句 Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  • StoreStore屏障: 对于这样的语句 Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  • LoadStore屏障: 对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被执行前,保证Load1要读取的数据被读取完毕。
  • StoreLoad屏障: 对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的(冲刷写缓冲器,清空无效化队列)。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

Java中对内存屏障的使用在一般的代码中不太容易见到,常见的有volatile和synchronized关键字修饰的代码块(后面再展开介绍),还可以通过Unsafe这个类来使用内存屏障。

4.4 8种操作同步的规则

JMM在执行前面介绍8种基本操作时,为了保证内存间数据一致性,JMM中规定需要满足以下规则:

  • 规则1:如果要把一个变量从主内存中复制到工作内存,就需要按顺序的执行 read 和 load 操作,如果把变量从工作内存中同步回主内存中,就要按顺序的执行 store 和 write 操作。但 Java 内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
  • 规则2:不允许 read 和 load、store 和 write 操作之一单独出现。
  • 规则3:不允许一个线程丢弃它的最近 assign 的操作,即变量在工作内存中改变了之后必须同步到主内存中。
  • 规则4:不允许一个线程无原因的(没有发生过任何 assign 操作)把数据从工作内存同步回主内存中。
  • 规则5:一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load 或 assign )的变量。即就是对一个变量实施 use 和 store 操作之前,必须先执行过了 load 或 assign 操作。
  • 规则6:一个变量在同一个时刻只允许一条线程对其进行 lock 操作,但 lock 操作可以被同一条线程重复执行多次,多次执行 lock 后,只有执行相同次数的 unlock 操作,变量才会被解锁。所以 lock 和 unlock 必须成对出现。
  • 规则7:如果对一个变量执行 lock 操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行 load 或 assign 操作初始化变量的值。
  • 规则8:如果一个变量事先没有被 lock 操作锁定,则不允许对它执行 unlock 操作;也不允许去 unlock 一个被其他线程锁定的变量。
  • 规则9:对一个变量执行 unlock 操作之前,必须先把此变量同步到主内存中(执行 store 和 write 操作)

看起来这些规则有些繁琐,其实也不难理解:

  • 规则1、规则2 工作内存中的共享变量作为主内存的副本,主内存变量的值同步到工作内存需要read和load一起使用,工作内存中的变量的值同步回主内存需要store和write一起使用,这2组操作各自都是是一个固定的有序搭配,不允许单独出现。
  • 规则3、规则4 由于工作内存中的共享变量是主内存的副本,为保证数据一致性,当工作内存中的变量被字节码引擎重新赋值,必须同步回主内存。如果工作内存的变量没有被更新,不允许无原因同步回主内存。
  • 规则5 由于工作内存中的共享变量是主内存的副本,必须从主内存诞生。
  • 规则6、7、8、9 为了并发情况下安全使用变量,线程可以基于lock操作独占主内存中的变量,其他线程不允许使用或unlock该变量,直到变量被线程unlock。

4.5 volatile型变量的特殊规则

volatile的中文意思是不稳定的,易变的,用volatile修饰变量是为了保证变量的可见性。

volatile的语义

volatile主要有下面2种语义

语义1 保证可见性

保证了不同线程对该变量操作的内存可见性。

这里保证可见性是不等同于volatile变量并发操作的安全性,保证可见性具体一点解释:

线程写volatile变量的过程:

  • 1 改变线程工作内存中volatile变量副本的值
  • 2 将改变后的副本的值从工作内存刷新到主内存

线程读volatile变量的过程:

  • 1 从主内存中读取volatile变量的最新值到线程的工作内存中
  • 2 从工作内存中读取volatile变量的副本

但是如果多个线程同时把更新后的变量值同时刷新回主内存,可能导致得到的值不是预期结果:

举个例子: 定义volatile int count = 0,2个线程同时执行count++操作,每个线程都执行500次,最终结果小于1000,原因是每个线程执行count++需要以下3个步骤:

  • 步骤1 线程从主内存读取最新的count的值
  • 步骤2 执行引擎把count值加1,并赋值给线程工作内存
  • 步骤3 线程工作内存把count值保存到主内存 有可能某一时刻2个线程在步骤1读取到的值都是100,执行完步骤2得到的值都是101,最后刷新了2次101保存到主内存。

语义2 禁止进行指令重排序

具体一点解释,禁止重排序的规则如下:

  • 当程序执行到 volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
  • 在进行指令优化时,不能将在对 volatile 变量访问的语句放在其后面执行,也不能把 volatile 变量后面的语句放到其前面执行。

普通的变量仅仅会保证该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证赋值操作的顺序与程序代码中的执行顺序一致。

举个例子:

volatile boolean initialized = false;

// 下面代码线程A中执行
// 读取配置信息,当读取完成后将initialized设置为true以通知其他线程配置可用
doSomethingReadConfg();
initialized = true;

// 下面代码线程B中执行
// 等待initialized 为true,代表线程A已经把配置信息初始化完成
while (!initialized) {
     sleep();
}
// 使用线程A初始化好的配置信息
doSomethingWithConfig();

上面代码中如果定义initialized变量时没有使用volatile修饰,就有可能会由于指令重排序的优化,导致线程A中最后一句代码 "initialized = true" 在 “doSomethingReadConfg()” 之前被执行,这样会导致线程B中使用配置信息的代码就可能出现错误,而volatile关键字就禁止重排序的语义可以避免此类情况发生。

volatile型变量实现原理

具体实现方式是在编译期生成字节码时,会在指令序列中增加内存屏障来保证,下面是基于保守策略的JMM内存屏障插入策略:

  • 在每个volatile写操作的前面插入一个StoreStore屏障。 该屏障除了保证了屏障之前的写操作和该屏障之后的写操作不能重排序,还会保证了volatile写操作之前,任何的读写操作都会先于volatile被提交。

  • 在每个volatile写操作的后面插入一个StoreLoad屏障。 该屏障除了使volatile写操作不会与之后的读操作重排序外,还会刷新处理器缓存,使volatile变量的写更新对其他线程可见。

  • 在每个volatile读操作的后面插入一个LoadLoad屏障。 该屏障除了使volatile读操作不会与之前的写操作发生重排序外,还会刷新处理器缓存,使volatile变量读取的为最新值。

  • 在每个volatile读操作的后面插入一个LoadStore屏障。 该屏障除了禁止了volatile读操作与其之后的任何写操作进行重排序,还会刷新处理器缓存,使其他线程volatile变量的写更新对volatile读操作的线程可见。

volatile型变量使用场景

总结起来,就是“一次写入,到处读取”,某一线程负责更新变量,其他线程只读取变量(不更新变量),并根据变量的新值执行相应逻辑。例如状态标志位更新,观察者模型变量值发布。

4.6 final型变量的特殊规则

我们知道,final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。 final关键字的可见性是指:被final修饰的字段在声明时或者构造器中,一旦初始化完成,那么在其他线程无须同步就能正确看见final字段的值。这是因为一旦初始化完成,final变量的值立刻回写到主内存。

4.7 synchronized的特殊规则

通过 synchronized关键字包住的代码区域,对数据的读写进行控制:

  • 读数据 当线程进入到该区域读取变量信息时,对数据的读取也不能从工作内存读取,只能从内存中读取,保证读到的是最新的值。
  • 写数据 在同步区内对变量的写入操作,在离开同步区时就将当前线程内的数据刷新到内存中,保证更新的数据对其他线程的可见性。

4.8 long和double型变量的特殊规则

Java内存模型要求lock、unlock、read、load、assign、use、store、write这8种操作都具有原子性,但是对于64位的数据类型(long和double),在模型中特别定义相对宽松的规定:允许虚拟机将没有被volatile修饰的64位数据的读写操作分为2次32位的操作来进行。也就是说虚拟机可选择不保证64位数据类型的load、store、read和write这4个操作的原子性。由于这种非原子性,有可能导致其他线程读到同步未完成的“32位的半个变量”的值。

不过实际开发中,Java内存模型强烈建议虚拟机把64位数据的读写实现为具有原子性,目前各种平台下的商用虚拟机都选择把64位数据的读写操作作为原子操作来对待,因此我们在编写代码时一般不需要把用到的long和double变量专门声明为volatile。

5 总结

由于Java内存模型涉及系列规则,网上的文章大部分就是对这些规则进行解析,但是很多没有解释为什么需要这些规则,这些规则的作用,其实这是不利于初学者学习的,容易绕进去这些繁琐规则不知所以然,下面谈谈我的一点学习知识的个人体会:

学习知识的过程不是等同于只是理解知识和记忆知识,而是要对知识解决的问题的输入和输出建立连接,知识的本质是解决问题,所以在学习之前要理解问题,理解这个问题要的输出和输出,而知识就是输入到输出的一个关系映射。知识的学习要结合大量的例子来理解这个映射关系,然后压缩知识,华罗庚说过:“把一本书读厚,然后再读薄”,解释的就是这个道理,先结合大量的例子理解知识,然后再压缩知识。

以学习Java内存模型为例:

  • 理解问题,明确输入输出 首先理解Java内存模型是什么,有什么用,解决什么问题
  • 理解内存模型系列协议 结合大量例子理解这些协议规则
  • 压缩知识 大量规则其实就是通过数据同步协议,保证内存副本之间的数据一致性,同时防止重排序对程序的影响。

参考

《深入学习Java虚拟机》

深入拆解Java虚拟机

Java核心技术36讲

Synchronization and the Java Memory Model ——Doug Lea

深入理解 Java 内存模型

Java内存屏障和可见性

内存屏障与synchronized、volatile的原理

从内存加载DLL完整代码及示例 这是一个从内存(资源形式)直接加载并调用DLL中函数的例子。 xDll工程只是一个测试用的dll,附上代码,编译出的xDll.dll直接放在testLoadDll工程目录下 testLoadDll是实际测试代码,从资源直接加载Dll并调用其导出函数 加载用MemoryLoadLibrary() 查找导出函数用MemoryGetProcAddress() 释放时用MemoryFreeLibrary 详情请见代码。 by achillis QQ:344132161 立即下载

相关推荐

《深入理解JAVA内存模型》PDF

在java中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享(本文使用“共享变量”这个术语代指实例域,静态域和数组元素)。局部变量(Local variables),方法定义参数(java语言规范称之为formal method parameters)和异常处理器参数(exception handler parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。 Java线程之间的通信由Java内存模型(本文简称为JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。 。。。。。。

从内存中加载动态库

从内存中加载动态库 程序使用动态库DLL一般分为隐式加载和显式加载两种,分别对应两种链接情况。本文主要讨论显式加载的技术问题。我们知道,要显式加载一个DLL,并取得其中导出的函数地址一般是通过如下步骤:    (1) 用LoadLibrary加载dll文件,获得该dll的模块句柄;    (2) 定义一个函数指针类型,并声明一个变量;    (3) 用GetProcAddress取得该dll中

问天的专栏 1184

运算放大器测试方法-输入失调电压

由于运放输入回路的不对称性,在输入端将产生一定的误差信号,从而限制运放信号灵敏度。通常用输入失调电压(VOS)、输入失调电流(Ios)和输入偏置电流(IB)表示。运放有四个主要特征:输入失调特征、开环特征、共模特征及输出特征。其放大倍数较大,由于外界的影响,即使运放的输入端产生较小的输入信号,经过放大,在输出端也会产生较大的误差。为能精确测量其性能,一般采用辅助运放进行测试。下面着重讲了运放的主要参数及如何精确测量这些参数。文中DUT代表被测运放,A代表辅助运放。

weixin_46135931的博客 1万+

Intel Core & Enhanced Core架构/微架构/流水线 (14) - 存储器/内存读写 Memory Load/Store

Loads 当指令从回写类型的存储器中读取某个地址时,处理器会按照如下的规则从高速缓存或存储器中查找数据(确切的说是匹配地址): 发起核(即执行读存指令的处理器核)的一级数据缓存 其他核的一级数据缓存和二级缓存 系统存储器 在第二步中,如果命中其他核的一级数据缓存且数据是修改状态,则从其他核中获取数据,此时会忽略二级缓存行返回的数据及数据状态。 下表是从存储器簇中不同的层级部件中的某存储...

一凡stkeke 932

app逆向分析案例-95分

先把justtrusmeplus打开设置好95分,开始抓包,得到结果为: https://www.95fenapp.com/api_goods/list/v3.0?is_all=1&keywords=%E7%A7%91%E6%AF%94& page=1&page_size=20 &publish_timestamp=0 &scene=2&scene_type=95fen_android_search_personal&sn=SearchList

weixin_41763171的博客 2742

【基于CKF的IMM】CV和CT两个模型下的IMM,二维,滤波使用CKF(容积卡尔曼滤波),附完整的matlab代码

该文章摘要: 本文提出了一种基于交互多模型(IMM)和容积卡尔曼滤波(CKF)的融合定位算法,用于解决目标在直线运动和转弯机动之间的模型切换问题。通过MATLAB仿真实现了IMM-CKF算法,并与单一的CV-CKF(匀速模型)和CT-CKF(匀角速转弯模型)方法进行对比。实验结果表明,在包含直线段和转弯段的复合轨迹跟踪中,IMM-CKF方法能够自适应调整模型权重,显著提高了定位精度。轨迹跟踪图、位置/速度误差对比图以及模型概率曲线直观展示了IMM算法的优势。文章提供了完整的MATLAB实现代码,可用于目标跟

985工科博士毕业,专攻定位、导航和滤波等算法研究。从业10年,主要使用MATLAB 615

隐藏模块(无模块注入)

隐藏模块(无模块注入)实现 隐藏模块(无模块注入) 代码 // memoryLoad.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "stdlib.h" #include "PeTools.h" DWORD WINAPI InjectEntry(LPVOID lpParam) { //RepairIAT(lpParam); //定义PE头的信息 PIMAGE_DO

qq_15900895的博客 1733

【Python LeetCode 专题】面试经典 150 题

面试经典 150 题

一杯水果茶!足矣~ 1173

深入理解Java内存模型

多任务处理在现代计算机系统中几乎是一项必备的功能了,让计算机同时去做多个事情,并不是因为计算机的处理能力强大,而是因为计算机的运算速度和子系统的速度差距很大,如果计算机不去同时处理多任务,大量的时间都浪费在了磁盘IO上,网络通信和数据库访问上,而在等待磁盘IO等耗时操作时计算机却不能做其他事情,所以我们要去“压榨”计算机的运算能力,为了让计算机同时做多件事情,就有了多线程。...

weixin_46017166的博客 591

01 Python学习总结:Python简介及入门

Python 是一门高级编程语言,以其简洁、易读和强大的功能而广受欢迎。它适用于多种应用场景,包括 Web 开发、数据分析、人工智能、自动化脚本等。以下是 Python 的简介及入门知识总结,帮助你快速了解并开始学习 Python。

数据知道的博客 1万+

Python 爬虫实战:解析 JSON 数据爬取今日头条热点新闻(API 接口逆向与字段提取)

在当今信息爆炸的时代,新闻资讯的获取变得尤为重要。今日头条作为国内领先的新闻聚合平台,其热点新闻数据具有很高的价值。本文将详细介绍如何通过 Python 爬虫技术,逆向解析今日头条的 API 接口,提取并解析 JSON 数据,最终获取热点新闻信息。

yansideyucsdn的博客 3210

深入理解java内存模型_《深入理解Java内存模型》读书总结

概要文章是《深入理解Java内容模型》读书笔记,该书总共包括了3部分的知识。第1包括“并发、同步、主内存、本地内存、重排序、内存屏障、happens before规则、as-if-serial规则、数据依赖性、顺序一致性模型、JMM的含义和意义”。第2该部分中就介绍了“同步”的3种方式:volatile、锁、final。对于每一种方式,从该方式的“特性”、“建立的happens before关系”...

weixin_29237635的博客 133

出现 Transaction rolled back because it has been marked as rollback-only 解决方法

出现 Transaction rolled back because it has been marked as rollback-only 解决方法

码农研究僧的博客 1万+

java steam 去重_深入理解 Java 内存模型

前提《深入理解 Java 内存模型》程晓明著,该书在以前看过一遍,现在学的东西越多,感觉那块越重要,于是又再细看一遍,于是便有了下面的读书笔记总结。全书页数虽不多,内容讲得挺深的。细看的话,也是挺花时间的,看完收获绝对挺大的。也建议 Java 开发者都去看看。里面主要有 Java 内存模型的基础、重排序、顺序一致性、Volatile 关键字、锁、final。本文参考书中内容。基础并发编程...

weixin_36484814的博客 147

YOLOv11改进 | 主干/Backbone篇 | 利用RT-DETR主干网络PPHGNetV2助力yolov11有效涨点(轻量化目标检测网络)

本文给大家带来利用RT-DETR模型主干HGNet去替换YOLOv11的主干,RT-DETR是今年由百度推出的第一款实时的ViT模型,其在实时检测的领域上号称是打败了YOLO系列,其利用两个主干一个是HGNet一个是ResNet,其中HGNet就是我们今天来讲解的网络结构模型,这个网络结构目前还没有推出论文,所以其理论知识在网络上也是非常的少,我也是根据网络结构图进行了分析(精度mAP提高0.05)。YOLOv11改进系列专栏——本专栏持续复习各种顶会内容——科研必备​目录一、本文介绍。

Snu77的博客 3719

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

转《深入理解 Java 内存模型》读书笔记

转:https://mp.weixin.qq.com/s/2hA6u4hLEPWlTPdD-XB-bg 前提 《深入理解 Java 内存模型》程晓明著,该书在以前看过一遍,现在学的东西越多,感觉那块越重要,于是又再细看一遍,于是便有了下面的读书笔记总结。全书页数虽不多,内容讲得挺深的。细看的话,也是挺花时间的,看完收获绝对挺大的。也建议 Java 开发者都去看看。里面主要有 Java 内存模型...

weixin_30648587的博客 81

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

深入理解Java内存模型之系列篇

转自:http://blog.youkuaiyun.com/ccit0519/article/details/11241403 原文:http://www.infoq.com/cn/articles/java-memory-model-1?utm_source=infoq&utm_medium=related_content_link&utm_campaign=relatedContent...

iteye_7202的博客 120

蓝牙模块HM-10手册

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

FOC电流环PI参数别再靠手感:极点对消法推导与实测避坑指南

电机控制系统的动态品质,往往取决于最内层电流环的PI参数整定。在很多工程实践中,工程师习惯用试凑法调参,却常因参数维度高、难以迁移而陷入振荡或饱和的困境。电流环本质上控制的是电机绕组构成的RL回路,其传递函数是一个典型的一阶惯性系统。借助极点对消法,令PI控制器的零点抵消电机模型的极点,即可将Kp和Ki直接由电机电感L、电阻R与期望带宽推导出来,实现从“凭手感”到“按公式”的转变。合理选择带宽并完成标幺值与离散化映射,就能在TI C2000、STM32等平台上快速获得可靠初值。本文结合实测代码框架与常见坑点

weixin_34297704的博客 316

java steam 去重_深入理解 Java 内存模型读书笔记

点击蓝色“SpringForAll社区”关注我哟前提《深入理解 Java 内存模型》程晓明著,该书在以前看过一遍,现在学的东西越多,感觉那块越重要,于是又再细看一遍,于是便有了下面的读书笔记总结。全书页数虽不多,内容讲得挺深的。细看的话,也是挺花时间的,看完收获绝对挺大的。也建议 Java 开发者都去看看。里面主要有 Java 内存模型的基础、重排序、顺序一致性、Volatile 关键字...

weixin_31653971的博客 130

深入理解 Java 虚拟机:Java 内存模型与线程

深入理解 Java 虚拟机:Java 内存模型与线程什么是内存模型?什么是高速缓存?缓存一致性Java 内存模型主内存工作内存内存间的交互操作volatile 关键字对所有线程可见性禁止指令重排序Java 内存模型的特征原子性可见性有序性synchronized 缺点先行发生原则“天然的” 先行发生关系 什么是内存模型? 内存模型: 可以理解为,在特定的操作协议下,对特点的内存或高速缓存进行读写访问的过程的抽象。 什么是高速缓存? 在运算时,将需要使用到的数据从内存复制到缓存(Cache)中,以此让计算能更

乐之终曲 1709
上一篇: idea的快捷键技巧与总结
下一篇: ConcurrentHashMap源码分析
有理唔理
博客等级 码龄10年 636粉丝 31原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值