-
多线程分类的最新文章
-
最新评论
-
咖啡:加油~
-
wx5951c76fc3280:好的,谢谢支持
-
目录
-
共享内存模型
在共享内存的并发模型里面,线程之间共享程序的公共状态,线程之间通过读写内存中公共状态来进行隐式通信
该内存指的是主内存,实际上是物理内存的一小部分
线程安全 : 局部变量、方法定义的参数、异常处理器参数是当前线程的虚拟机栈中的数据,并且不会进行线程共享,所以不会存在内存可见性问题
由上图能看出来线程间的通讯都是通过主内存来进行传递消息的, 每个线程在进行共享数据处理的时候都是将共享的数据复制到当前线程本地(每个线程自己都有一个内存)来进行操作。
整个数据交互的过程是JMM控制的,主要控制主内存与每个线程的本地内存如何进行交互来提供共享数据的可见性
程序在执行的时候为了提高效率会将程序指令进行重新排序
编译器在不改变单线程程序语义的情况下进行语句执行顺序的优化
如果不存在数据的依赖性的话,处理器可以改变语句对应机器指令的执行顺序
由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行
以上三种重排序都会导致我们在写并发程序的时候出现内存可见性的问题。
JMM的编译器重排序规则会禁止特定类型的编译器重排序;
JMM的处理器重排序规则会要求java编译器在生成指令序列的时候插入特定的内存屏障指令,通过内存屏障指令来禁止特定类型的处理器进行重排序
由于为了避免处理器等待向内存中写入数据的延时,在处理器和内存中间加了一个缓冲区,这样处理器可以一直向缓冲区中写入数据,等到一定时间将缓冲区的数据一次性的刷入到内存中。
优点 :
缺点 :
例如以下场景 :
在当前场景中就可能出现在处理器 A 和处理器 B 没有将它们各自的写缓冲区中的数据刷回内存中, 将内存中读取的A = 0、B = 0 进行给X和Y赋值,此时将缓冲区的数据刷入内存,导致了最后结果和实际想要的结果不一致。因为只有将缓冲区的数据刷入到了内存中才叫真正的执行
以上主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存,如何从工作内存同步到主内存之间的实现细节,JMM定义了以下8种操作来完成
操作 | 语义解析 |
---|---|
lock(锁定) | 作用于主内存的变量,把一个变量标记为一条线程独占状态 |
unlock(解锁) | 作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定 |
read(读取) | 作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用 |
load(载入) | 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中 |
use(使用) | 作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎 |
assign(赋值) | 作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量 |
store(存储) | 作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,<br>以便随后的write的操作 |
write(写入) | 作用于工作内存的变量,它把store操作从工作内存中的一个变量的值传送<br>到主内存的变量中 |
如果要把一个变量从主内存中复制到工作内存中,就需要按顺序地执行read和load操作,如果把变量从工作内存中同步到主内存中,就需要按顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行
操作执行流程图解:
同步规则分析
为了解决处理器重排序导致的内存错误,java编译器在生成指令序列的适当位置插入内存屏障指令,来禁止特定类型的处理器重排序
内存屏障指令
屏障类型 | 指令示例 | 说明 |
---|---|---|
LoadLoadBarriers | Load1;LoadLoad;Load2 | Load1数据装载发生在Load2及其所有后续数据装载之前 |
StoreStoreBarriers | Store1;StoreStore;Store2 | Store1数据刷回主存要发生在Store2及其后续所有数据刷回主存之前 |
LoadStoreBarriers | Load1;LoadStore;Store2 | Load1数据装载要发生在Store2及其后续所有数据刷回主存之前 |
StoreLoadBarriers | Store1;StoreLoad;Load2 | Store1数据刷回内存要发生在Load2及其后续所有数据装载之前 |
happens-before 原则来辅助保证程序执行的原子性、可见性以及有序性的问题,它是判断数据是否存在竞争、线程是否安全的依据
在JMM中如果一个操作中的结果需要对另一个操作可见,那么这两个操作之前必须要存在happens-before关系 (两个操作可以是同一个线程也可以不是一个线程)
规则内容:
注意: 两个操作之间具有 happens-before 关系,并不意味着前一个操作必须要在后一个操作之前执行,只需要前一个操作的结果对后一个操作可见,并且前一个操作按顺序要排在后一个操作之前。
就是前一个操作的结果对后一个操作的结果产生影响,此时编译器和处理器在处理当前有数据依赖性的操作时不会改变存在数据依赖的两个操作的执行顺序
注意: 此时所说的数据依赖仅仅针对单个处理器中执行的指令序列或者单个线程中执行的操作。不同处理器和不同线程的情况编译器和处理器是不会考虑的
在单线程情况下不管怎么重排序程序的执行结果不能被改变,所以如果在单处理器或者单线程的情况下,编译器和处理器对于有数据依赖性的操作是不会进行重排序的。反之如果没有数据依赖性的操作就有可能发生指令重排。
在多线程情况下才会出现数据竞争
在一个线程中写了一个变量,在另一个线程中读一个变量,而且写和读并没有进行同步
如果在多线程条件下,程序能够正确的使用同步机制,那么程序的执行将具有顺序一致性(就像在单线程条件下执行一样) 程序最终运行的结果与你预期的结果一样
4.3.1特性:
4.3.2概念:
在概念上,顺序一致性有一个单一的全局内存,在任意时间点最多只有一个线程可以连接到内存,当在多线程的场景下,会把所有内存的读写操作变成串行化
4.3.3案例:
例如有多个并发线程 A B C, A 线程有两个操作 A1 A2, 他们的执行的顺序是 A1 -> A2 。B 线程有三个操作 B1 B2 B3, 他们的执行的顺序是 B1 -> B2 ->B3 。C 线程有两个操作 C1 C2 那么他们在程序中执行的顺序是 C1 -> C2 。
场景分析 :
场景一 : 并发安全(同步)执行顺序
A1 -> A2 -> B1 -> B2 ->B3 -> C1 -> C2
场景二: 并发不安全(非同步)执行顺序
A1 -> B1 -> A2 -> C1 -> B2 ->B3 -> C2
结论 :
在非同步的场景下,即使三个线程中的每一个操作乱序执行,但是在每个线程中的各自操作还是保持有序的。并且所有线程都只能看到一个一致的整体执行顺序,也就是说三个线程看到的都是该顺序 : A1 -> B1 -> A2 -> C1 -> B2 ->B3 -> C2 ,因为顺序一致性内存模型中的每个操作必须立即对任意线程可见。
以上案例场景在JMM中不是这样的,未同步的程序在JMM中不仅整体的执行顺序变了,就连每个线程的看到的操作执行顺序也是不一样的。
例如前面所说的如果线程A将变量的值 a = 2 写入到了自己的本地内存中,还没有刷入到主存中,在线程 A 来看值是变了,但是其他线程 B 线程 C 根本看不到值的改变,就认为线程A 的操作还没有发生,只有线程 A 将工作内存中的值刷回主内存线程 B和线程C 才能的到。但是如果是同步的情况下,顺序一致性模型和JMM模型执行的结果是一致的,但是程序的执行顺序不一定,因为在JMM中,会发生指令重排现象所以执行顺序会不一致。
1
收藏
Ctrl+Enter 发布
发布
取消