并发三大特性

并发三大特性,第1张

并发三大特性

并发编程

并发和并行并发三大特性

可见性

如何保证可见性 有序性

如何保证有序性 原子性

如何保证原子性 Java内存模型

JMM定义JMM与硬件内存架构的关系

内存交互 *** 作JMM的内存可见性保证 volatile的内存语义

volatile的特性volatile写-读的内存语义 volatile可见性实现原理

JMM内存交互层面实现硬件层面 有序性深入分析

指令重排序volatile 重排序规则

并发和并行

目标都是最大化CPU的使用率
并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。

并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。

并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器系统中存在是因为并发是并行的假象,并行要求程序能够同时执行多个 *** 作,而并发只是要求程序假装同时执行多个 *** 作(每个小时间片执行一个 *** 作,多个 *** 作快速切换执行)

并发三大特性

并发:可见性、原子性和有序性。

可见性

当一个线程修改了共享变量的值,其他线程能够看到修改的值。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方法来实现可见性。

如何保证可见性

通过 volatile 关键字保证可见性通过内存屏障保证可见性通过 synchronized 关键字保证可见性通过 lock 保证可见性通过 final 关键子保证可见性 有序性

即程序执行的顺序按照代码的先后顺序执行。JVM存在指令重排,所以存在有序性的问题。

如何保证有序性

通过 volatile 关键字保证有序性通过内存屏障保证有序性通过 synchronized 关键字保证有序性通过 lock 保证有序性 原子性

一个或多个 *** 作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行。在Java 中,对基本数据类型的变量的读取和赋值 *** 作是原子性 *** 作(64位处理器)。不采取任何的原子性保障措施的自增 *** 作并不是原子性的

如何保证原子性

通过 synchronized 关键字保证原子性通过 lock保证原子性通过cas保证原子性
思考:在 32 位的机器上对 long 型变量进行加减 *** 作是否存在并发隐患? Java内存模型 JMM定义

Java虚拟机规范中定义了Java内存模型(Java Memory Model,JMM),用于屏蔽掉各个硬件和 *** 作系统的内存访问差异,以实现让Java程序在各平台下都能达到一致的并发效应,JMM规范了虚拟机与计算机是如何协同工作的:规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。JMM描述的是一种抽象得到概念,一组规则,通过这组规则控制程序中各个变量在共享数据区域和私有数据区域的访问方式,JMM是围绕原子性、有序性、可见性展开的

JMM与硬件内存架构的关系

Java内存模型与硬件内存架构之间存在差异。硬件内存架构没有区分线程栈和堆。对于硬件,所有的线程栈和堆都分布在主内存中。部分线程栈和堆可能有时候会出现在CPU缓存中和CPU内部的寄存器中。

内存交互 *** 作

关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,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 *** 作之前,必须先执行过了assign和load *** 作一个变量在同一时刻只允许一条线程对其进行lock *** 作,但lock *** 作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock *** 作,变量才会被解锁。lock和unlock必须成对出现如果对一个变量执行lock *** 作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign *** 作初始化变量的值如果一个变量事先没有被lock *** 作锁定,则不允许对它执行unlock *** 作;也不允许
去unlock一个被其他线程锁定的变量对一个变量执行unlock *** 作之前,必须先把此变量同步到主内存中(执行store和
write *** 作) JMM的内存可见性保证

按程序类型,Java程序的内存可见性保证可以分为下列3类:

单线程程序。单线程程序不会出现内存可见性问题。编译器、runtime和处理器会
共同确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同正确同步的多线程程序。正确同步的多线程程序的执行将具有顺序一致性(程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同)。这是JMM关注的重点,JMM通过限制编译器和处理器的重排序来为程序员提供内存可见性保证未同步/未正确同步的多线程程序。JMM为它们提供了最小安全性保障:线程执行
时读取到的值,要么是之前某个线程写入的值,要么是默认值未同步程序在JMM中的执行时,整体上是无序的,其执行结果无法预知。 JMM不保证未同步程序的执行结果与该程序在顺序一致性模型中的执行结果一致

未同步程序在JMM中的执行时,整体上是无序的,其执行结果无法预知。未同步程序在两个模型中的执行特性有如下几个差异。

顺序一致性模型保证单线程内的 *** 作会按程序的顺序执行,而JMM不保证单线程内的 *** 作会按程序的顺序执行,比如正确同步的多线程程序在临界区内的重排序顺序一致性模型保证所有线程只能看到一致的 *** 作执行顺序,而JMM不保证所有线程能看到一致的 *** 作执行顺序顺序一致性模型保证对所有的内存读/写 *** 作都具有原子性,而JMM不保证对64位的long型和double型变量的写 *** 作具有原子性(32位处理器)

JVM在32位处理器上运行时,可能会把一个64位long/double型变量的写 *** 作拆分为两个32位的写 *** 作来执行。这两个32位的写 *** 作可能会被分配到不同的总线事务中执行,此时对这个64位变量的写 *** 作将不具有原子性。从JSR-133内存模型开始(即从JDK5开始),仅仅只允许把一个64位long/double型变量的写 *** 作拆分为两个32位的写 *** 作来执行,任意的读 *** 作在JSR-133中都必须具有原子性

volatile的内存语义 volatile的特性

可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最
后的写入原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复
合 *** 作不具有原子性(基于这点,我们通过会认为volatile不具备原子性)。volatile仅仅保证对单个变量的读/写具有原子性,而锁的互斥执行的特性可以确保对整个临界区代码的执行具有原子性。

64位的long型和double型变量,只要它是volatile变量,对该变量的读/写就具有原子性。

有序性:对volatile修饰变量的读写 *** 作前后加上各种特定的内存屏障来禁止指令重排序来保障有序性。

在JSR-133之前的旧Java内存模型中,虽然不允许volatile变量之间重排序,但旧的Java内存模型允许volatile变量与普通变量重排序。为了提供一种比锁更轻量级的线程之间通信的机制,JSR-133专家组决定增强volatile的内存语义:严格限制编译器和处理器对volatile变量与普通变量的重排序,确保volatile的写-读和锁的释放-获取具有相同的内存语义。

volatile写-读的内存语义

当写一个volatile变量,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。当读一个volatile变量,JMM会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量。(基于这点,我们通常会认为volatile不具备原子性 ) volatile可见性实现原理 JMM内存交互层面实现

volatile修饰变量的read、load、use *** 作和assign、store、write必须是连续的,即修改后必须立即同步到回主内存,使用时必须从主内存刷新,由此保证volatile变量 *** 作对多线程的可见性。

硬件层面

通过lock前缀指令,回锁定变量缓存行区域写回主内存,这个 *** 作称为“缓存锁定”,缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据。一个处理器的缓存回写到内存会导致其他处理器的缓存无效。

有序性深入分析 指令重排序

Java语言规范规定JVM线程内部维持顺序化语义。即只要程序的最终结果与它顺序化情况的结果相等,那么指令的执行顺序可以与代码顺序不一致,此过程叫指令的重排序。指令重排序的意义:JVM能根据处理器特性(CPU多级缓存系统、多核处理器等)适当的对机器指令进行重排序,使机器指令能更符合CPU的执行特性,最大限度的发挥机器性能。在编译器与CPU处理器中都能执行指令重排优化 *** 作

volatile 重排序规则 是否能够重排序 *** 作2 普通读/写 *** 作2 volatile 读 *** 作2 volatile 写 *** 作1 普通读/写NO *** 作1 volatile 读NONONO *** 作1 volatile 写NONO

volatile禁止重排序场景:

    *** 作2 是volatile写,不管 *** 作1 是什么都不会重排序 *** 作1 是volatile读,不管 *** 作2 是什么都不会重排序 *** 作1 是volatile写, *** 作2 是volatile读,也不会发生重排序

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5722280.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-18
下一篇 2022-12-18

发表评论

登录后才能评论

评论列表(0条)

保存