《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile(三)

简介: 《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile

五、join 原理

join 体现的是【保护性暂停】模式,请参考之

  • 保护性暂停:一个线程等待另一个线程的结果。
  • join:一个线程等待另一个线程的结束。

634fa8a6775c56b575b2eba6fc1ecb6e.png

六、park & unpark原理

每个线程都有自己的一个 Parker 对象,由三部分组成_counter _cond_mutex 打个比喻,线程就像一个旅人。

Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter 就好比背包中的备用干粮(0 为耗尽,1 为充足)


调用 park 就是要看需不需要停下来歇息


如果备用干粮耗尽,那么钻进帐篷歇息

如果备用干粮充足,那么不需停留,继续前进

调用 unpark,就好比令干粮充足


如果这时线程还在帐篷,就唤醒让他继续前进

如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留继续前进

因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮


8edaf82fae33457ebfc7f1038dced174.png


  1. 当前线程调用 Unsafe.park() 方法
  2. 检查 _counter ,本情况为 0,这时,获得 _mutex 互斥锁
  3. 线程进入阻塞队列, _cond 条件变量变为阻塞
  4. 设置 _counter = 0


9307bcb5f38abd137ad0796de3c64d51.png


  1. 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
  2. 唤醒 _cond 条件变量中的 Thread_0
  3. Thread_0 恢复运行
  4. 设置 _counter 为 0

0c14d07cd9d3210cec9d605332ff178b.png


  1. 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
  2. 当前线程调用 Unsafe.park() 方法
  3. 检查 _counter ,本情况为 1,这时线程无需阻塞,继续运行
  4. 设置 _counter 为 0


七、指令级并行原理

1.名词

Clock Cycle Time


主频的概念大家接触的比较多,而 CPU 的 Clock Cycle Time(时钟周期时间),等于主频的倒数,意思是 CPU 能够识别的最小时间单位,比如说 4G 主频的 CPU 的 Clock Cycle Time 就是 0.25 ns,作为对比,我们墙上挂钟的Cycle Time 是 1s


例如,运行一条加法指令一般需要一个时钟周期时间


CPI


有的指令需要更多的时钟周期时间,所以引出了 CPI (Cycles Per Instruction)指令平均时钟周期数


IPC


IPC(Instruction Per Clock Cycle) 即 CPI 的倒数,表示每个时钟周期能够运行的指令数


CPU 执行时间


程序的 CPU 执行时间,即我们前面提到的 user + system 时间,可以用下面的公式来表示


程序 CPU 执行时间 = 指令数 * CPI * Clock Cycle Time


2.鱼罐头的故事

加工一条鱼需要 50 分钟,只能一条鱼、一条鱼顺序加工…

fc246b998dfa58931129e9d2324c80a2.png


可以将每个鱼罐头的加工流程细分为 5 个步骤:

  • 去鳞清洗 10分钟
  • 蒸煮沥水 10分钟
  • 加注汤料 10分钟
  • 杀菌出锅 10分钟
  • 真空封罐 10分钟

55d702e5c765985a726c251d2d02dc2e.png


即使只有一个工人,最理想的情况是:他能够在 10 分钟内同时做好这 5 件事,因为对第一条鱼的真空装罐,不会影响对第二条鱼的杀菌出锅…


3.指令重排序优化


事实上,现代处理器会设计为一个时钟周期完成一条执行时间最长的 CPU 指令。为什么这么做呢?可以想到指令还可以再划分成一个个更小的阶段,例如,每条指令都可以分为: 取指令 - 指令译码 - 执行指令 - 内存访问 - 数据写回 这 5 个阶段


7a1c03d22db04747aa4fc5721b2c8a1f.png

术语参考:


instruction fetch (IF)

instruction decode (ID)

execute (EX)

memory access (MEM)

register write back (WB)

在不改变程序结果的前提下,这些指令的各个阶段可以通过重排序和组合来实现指令级并行,这一技术在 80’s 中叶到 90’s 中叶占据了计算架构的重要地位。


指令重排的前提是,重排指令不能影响结果,例如

// 可以重排的例子
int a = 10; // 指令1
int b = 20; // 指令2
System.out.println( a + b );
// 不能重排的例子
int a = 10; // 指令1
int b = a - 5; // 指令2


4.支持流水线的处理器


现代 CPU 支持多级指令流水线,例如支持同时执行 取指令 - 指令译码 - 执行指令 - 内存访问 - 数据写回 的处理器,就可以称之为五级指令流水线。这时 CPU 可以在一个时钟周期内,同时运行五条指令的不同阶段(相当于一条执行时间最长的复杂指令),IPC = 1,本质上,流水线技术并不能缩短单条指令的执行时间,但它变相地提高了指令地吞吐率。


提示:

奔腾四(Pentium 4)支持高达 35 级流水线,但由于功耗太高被废弃


7f020c18e0845a080bef91c4d18c275d.png

5.SuperScalar 处理器

大多数处理器包含多个执行单元,并不是所有计算功能都集中在一起,可以再细分为整数运算单元、浮点数运算单元等,这样可以把多条指令也可以做到并行获取、译码等,CPU 可以在一个时钟周期内,执行多于一条指令,IPC > 1


bce2a9708f7cdb47b344dcf219c413ff.png

2cbd6881e7c4a1ff7d049b74d6737170.png

八、CPU 缓存结构原理

1. CPU 缓存结构

9407816f4bb41d6dd1bababee2a5ab55.png

查看CPU缓存

[root@lxyStudy ~]# lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    1
Core(s) per socket:    1
座:                 2
NUMA 节点:         1
厂商 ID:           AuthenticAMD
CPU 系列:          23
型号:              96
型号名称:        AMD Ryzen 5 4500U with Radeon Graphics
步进:              1
CPU MHz:             2370.548
BogoMIPS:            4741.09
超管理器厂商:  VMware
虚拟化类型:     完全
L1d 缓存:          32K
L1i 缓存:          32K
L2 缓存:           512K
L3 缓存:           8192K
NUMA 节点0 CPU:    0,1


速度比较


8969ec72c3834aab1c217434b4c62324.png


查看 cpu 缓存行

[root@lxyStudy ~]# cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
64  //代表64个字节

cpu 拿到的内存地址格式是这样的

[高位组标记][低位索引][偏移量]

c1d6b1e1770872a7467cca582f84a86d.png

2. CPU 缓存读

读取数据流程如下

  • 根据低位,计算在缓存中的索引
  • 判断是否有效
  • 0 去内存读取新数据更新缓存行
  • 1 再对比高位组标记是否一致
  • 一致,根据偏移量返回缓存数据
  • 不一致,去内存读取新数据更新缓存行

3. CPU 缓存一致性

MESI 协议

  1. E、S、M 状态的缓存行都可以满足 CPU 的读请求
  2. E 状态的缓存行,有写请求,会将状态改为 M,这时并不触发向主存的写
  3. E 状态的缓存行,必须监听该缓存行的读操作,如果有,要变为 S 状态

981ede41ee6f8f1226724c5e00642d03.png

M 状态的缓存行,必须监听该缓存行的读操作,如果有,先将其它缓存(S 状态)中该缓存行变成 I 状态(即6.的流程),写入主存,自己变为 S 状态

S 状态的缓存行,有写请求,走 4. 的流程

S 状态的缓存行,必须监听该缓存行的失效操作,如果有,自己变为 I 状态

I 状态的缓存行,有读请求,必须从主存读取


00897c061cd1fd7d5f56327ad1e72ae6.png


相关文章
|
7月前
|
监控 安全
并发编程系列教程(06) - 多线程之间通讯(wait、notify、sleep、Lock锁、Condition)
并发编程系列教程(06) - 多线程之间通讯(wait、notify、sleep、Lock锁、Condition)
39 0
|
2天前
|
Java 机器人 程序员
Java中的线程通信:wait、notify与Condition详解
Java中的线程通信:wait、notify与Condition详解
|
28天前
|
安全 Java
Java的线程同步与通信:深入理解wait、notify和synchronized
Java的线程同步与通信:深入理解wait、notify和synchronized
19 0
|
10月前
|
Java C语言 C++
JUC--start线程
native:本地方法栈,c语言的函数,但凡调用的是这个接口,就是调用底层操作系统,第三方c语言的接口。 也就是说多线程和语言无关,是操作系统层面的东西。基本上每种语言都有自己的多线程实现方式。
|
安全 Java C++
JUC在深入面试题——三种方式实现线程等待和唤醒(wait/notify,await/signal,LockSupport的park/unpark)
JUC在深入面试题——三种方式实现线程等待和唤醒(wait/notify,await/signal,LockSupport的park/unpark)
172 1
JUC在深入面试题——三种方式实现线程等待和唤醒(wait/notify,await/signal,LockSupport的park/unpark)
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile(四)
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile(四)
|
存储 Java 编译器
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile(二)
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile(二)
|
存储 安全 Java
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile(一)
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile(一)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)(二)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)(二)
|
安全 Java API
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)(一)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)(一)