?并发编程的两大问题:如何通信&&如何同步
⭐️通信:线程之间以何种机制来交换信息
通信机制有两种方式:
1)共享内存➡️线程之间共享程序的公共状态➡️隐式通信
2)消息传递➡️无公共状态➡️显式通信
⭐️同步:用于控制不同线程见操作发生相对顺序的机制
1)共享内存➡️指定某个方法或某段代码需要在线程之间互斥执行➡️显式
2)消息传递➡️发送必须在接收之前➡️隐式
Java采用的是共享内存
?Java内存模型的抽象结构
线程之间共享堆内存➡️{所有实例域、静态域和数组元素}➡️共享变量
局部变量无内存可见性问题
Java内存模型决定一个线程对共享变量的写入何时对另一个线程可见。
抽象内存模型:1️⃣共享变量存在主内存中2️⃣每个线程都有一个本地内存(抽象的,实际不存在,涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化)
3️⃣本地内存中存储主内存共享变量的副本,用以读写
Java内存的抽象结构
Java内存的通信步骤
#主内存是针对计算机给的定义,堆内存是针对java虚拟机给的定义,二者并不独立
?指令重排序:从源代码到指令序列的重排序
源代码最后都要编程一个个指令在虚拟机上执行
三种重排序类型:
1)编译器优化的重排序
不改变单线程线程语义的情况下做重排序
2)指令集并行重排序
现代处理器支持将多条指令重叠执行,如果不存在数据依赖性,处理器可以改变执行顺序
3)内存系统的重排序
因为处理器使用缓存和读写缓冲期,是的加载和存储操作看上去乱序。理解的话,看下一节的内容就很容易懂啦。
源代码到最终执行指令,会分别进行三种重排序:
源代码➡️编译器优化重排序➡️指令集并行重排序➡️内存系统重排序➡️最终的指令序列
?并发编程模型的分类
缓冲区的作用
1)保证指令的持续运行,避免由于处理器停顿下来等待向内存写入数据而产生的延迟。
2)通过批处理方式刷新缓冲区,以及合并写缓冲区中对同一内存地址的多次写,减少对内存总线的占用。
带来的问题:
缓冲区只对自己的处理器可见,会对内存操作的执行顺序产生重要影响。
举个例子:
执行结果是x=y=0。按理说应该是x=2,y=1。
原因:当执行a1(写)操作时,a(=0)变量从主内存中被拷贝到本地内存,进行写操作,此时a在本地内存中。
当执行a2(读)操作时,b内存不在缓存中,则程序访问主内存访问b,而此时,B线程对b(=0)的改写在B的本地内存中。
所以x=y=0
也就是写操作没有刷新到内存的时候,就有读操作了,两个操作一起完成后才更新内存。
这里的关键是,由于写缓冲区仅对自己的处理器可见,它会导致处理器执行内存操作的顺序可能会与内存实际操作执行顺序不一致。
?happens-before简介(了解)
如果一个操作执行的结果需要对另一个操作可见,那这两个操作之间必须要存在happens-before关系。
相关规则如下:
- 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
- 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
- volatile变量规则:对一个valatile域的写,happens-before于任意后续对这个域的读。
- 传递性:a happens-before b b happens-before c a happens-before c