mirror of
https://github.com/youthlql/JavaYouth.git
synced 2026-03-13 21:33:42 +08:00
更新所有文章的图床,旧图床由于一些原因可能会逐渐失效
This commit is contained in:
@@ -9,7 +9,7 @@ categories:
|
||||
- 原理
|
||||
keywords: Java并发,原理,源码
|
||||
description: 万字系列长文讲解Java并发-第一阶段-多线程基础知识。
|
||||
cover: 'https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/logo_1.png'
|
||||
cover: 'https://gitee.com/youthlql/randombg/raw/master/logo/Java_concurrency.png'
|
||||
abbrlink: efc79183
|
||||
date: 2020-10-05 22:40:58
|
||||
---
|
||||
@@ -31,13 +31,13 @@ date: 2020-10-05 22:40:58
|
||||
概念:进程可进一步细化为线程,是一个程序内部的一条执行路径。
|
||||
说明:线程作为CPU调度和执行的单位,每个线程拥独立的运行栈和程序计数器(pc),线程切换的开销小。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0001.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0001.png">
|
||||
|
||||
|
||||
|
||||
补充:
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0002.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0002.png">
|
||||
|
||||
进程可以细化为多个线程。
|
||||
每个线程,拥有自己独立的:栈、程序计数器
|
||||
@@ -281,7 +281,7 @@ private void init(ThreadGroup g, Runnable target, String name,
|
||||
1、多线程的设计之中,使用了代理设计模式的结构,用户自定义的线程主体只是负责项目核心功能的实现,而所有的辅助实现全部交由Thread类来处理。
|
||||
2、在进行Thread启动多线程的时候调用的是start()方法,而后找到的是run()方法,但通过Thread类的构造方法传递了一个Runnable接口对象的时候,那么该接口对象将被Thread类中的target属性所保存,在start()方法执行的时候会调用Thread类中的run()方法。而这个run()方法去调用实现了Runnable接口的那个类所重写过run()方法,进而执行相应的逻辑。多线程开发的本质实质上是在于多个线程可以进行同一资源的抢占,那么Thread主要描述的是线程,而资源的描述是通过Runnable完成的。如下图所示:
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0003.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0003.png">
|
||||
|
||||
|
||||
|
||||
@@ -644,7 +644,7 @@ public void run() {
|
||||
|
||||
1、如果直接调用run()方法,相当于就是简单的调用一个普通方法。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0004.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0004.png">
|
||||
|
||||
2、run()的调用是在start0()这个Native C++方法里调用的
|
||||
|
||||
@@ -654,11 +654,11 @@ public void run() {
|
||||
|
||||
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态,这几个状态在Java源码中用枚举来表示。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0005.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0005.png">
|
||||
|
||||
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0006.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0006.png">
|
||||
|
||||
> 图中 wait到 runnable状态的转换中,`join`实际上是`Thread`类的方法,但这里写成了`Object`。
|
||||
|
||||
@@ -699,7 +699,7 @@ public static void main(String[] args) {
|
||||
|
||||
2、当JVM启动后,实际有多个线程,但是至少有一个非守护线程(比如main线程)。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0007.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0007.png">
|
||||
|
||||
- Finalizer:GC守护线程
|
||||
|
||||
@@ -1574,7 +1574,7 @@ volatile自己虽然不能保证原子性,但是和CAS结合起来就可以保
|
||||
|
||||
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0009.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0009.png">
|
||||
|
||||
|
||||
|
||||
@@ -1782,7 +1782,7 @@ public Unsafe getUnsafe() throws IllegalAccessException {
|
||||
|
||||
Unsafe的功能如下图:
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0008.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/First_stage/0008.png">
|
||||
|
||||
## CAS相关
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ categories:
|
||||
- 原理
|
||||
keywords: Java并发,原理,源码
|
||||
description: 万字系列长文讲解-Java并发体系-第三阶段-JUC并发包。JUC在高并发编程中使用频率非常高,这里会详细介绍其用法。
|
||||
cover: 'https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/logo_1.png'
|
||||
cover: 'https://gitee.com/youthlql/randombg/raw/master/logo/Java_concurrency.png'
|
||||
abbrlink: 5be45d9e
|
||||
date: 2020-10-09 22:13:58
|
||||
---
|
||||
@@ -1869,7 +1869,7 @@ public class ForkJoinRecursiveAction {
|
||||
|
||||
ForkJoinTask就是ForkJoinPool里面的每一个任务。他主要有两个子类:`RecursiveAction`和`RecursiveTask`。然后通过fork()方法去分配任务执行任务,通过join()方法汇总任务结果。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Third_stage/0001.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Third_stage/0001.png">
|
||||
|
||||
## 小总结
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ categories:
|
||||
- 原理
|
||||
keywords: Java并发,原理,源码
|
||||
description: 万字系列长文讲解-Java并发体系-第三阶段-JUC并发包。JUC在高并发编程中使用频率非常高,这里会详细介绍其用法。
|
||||
cover: 'https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/logo_1.png'
|
||||
cover: 'https://gitee.com/youthlql/randombg/raw/master/logo/Java_concurrency.png'
|
||||
abbrlink: 70c90e5d
|
||||
date: 2020-10-10 22:13:58
|
||||
---
|
||||
@@ -563,7 +563,7 @@ public int getUnarrivedParties()
|
||||
|
||||
根据上面的代码,我们可以画出下面这个很简单的图:
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Third_stage/0002.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Third_stage/0002.png">
|
||||
|
||||
这棵树上有 7 个 phaser 实例,每个 phaser 实例在构造的时候,都指定了 parties 为 5,但是,对于每个拥有子节点的节点来说,每个子节点都是它的一个 party,我们可以通过 phaser.getRegisteredParties() 得到每个节点的 parties 数量:
|
||||
|
||||
@@ -952,7 +952,7 @@ public class ThreadPoolDemo {
|
||||
|
||||
## 线程池的底层工作流程
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Third_stage/0003.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Third_stage/0003.png">
|
||||
|
||||
1、创建线程池后,等待请求任务
|
||||
|
||||
@@ -1646,7 +1646,7 @@ public class ExecutorCompletionService<V> implements CompletionService<V> {
|
||||
|
||||
**执行流程:**
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Third_stage/0004.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Third_stage/0004.png">
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ categories:
|
||||
- 原理
|
||||
keywords: Java并发,原理,源码
|
||||
description: '万字系列长文讲解-Java并发体系-第二阶段,从C++和硬件方面讲解。'
|
||||
cover: 'https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/logo_1.png'
|
||||
cover: 'https://gitee.com/youthlql/randombg/raw/master/logo/Java_concurrency.png'
|
||||
abbrlink: 230c5bb3
|
||||
date: 2020-10-06 22:09:58
|
||||
---
|
||||
@@ -291,7 +291,7 @@ d = e - f ;
|
||||
|
||||
由于处理器使用缓存和读写缓存冲区,这使得加载(load)和存储(store)操作看上去可能是在乱序执行,因为三级缓存的存在,导致内存与缓存的数据同步存在时间差。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0001.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0001.png">
|
||||
|
||||
|
||||
|
||||
@@ -343,7 +343,7 @@ int c = a + b;
|
||||
|
||||
冯诺依曼,提出计算机由五大组成部分,输入设备,输出设备存储器,控制器,运算器。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0002.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0002.png">
|
||||
|
||||
输入设备:鼠标,键盘等等
|
||||
|
||||
@@ -367,7 +367,7 @@ int c = a + b;
|
||||
|
||||
CPU的运算速度和内存的访问速度相差比较大。这就导致CPU每次操作内存都要耗费很多等待时间。内 存的读写速度成为了计算机运行的瓶颈。于是就有了在CPU和主内存之间增加缓存的设计。靠近CPU 的缓存称为L1,然后依次是 L2,L3和主内存,CPU缓存模型如图下图所示。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0003.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0003.png">
|
||||
|
||||
CPU Cache分成了三个级别: L1, L2, L3。级别越小越接近CPU,速度也更快,同时也代表着容量越小。速度越快的价格越贵。
|
||||
|
||||
@@ -377,7 +377,7 @@ CPU Cache分成了三个级别: L1, L2, L3。级别越小越接近CPU,速
|
||||
|
||||
3、L3 Cache是三级缓存中大的一级,例如12MB,同时也是缓存中慢的一级,在同一个CPU插槽 之间的核共享一个L3 Cache。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0004.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0004.png">
|
||||
|
||||
上面的图中有一个Latency指标。比如Memory这个指标为59.4ns,表示CPU在操作内存的时候有59.4ns的延迟,一级缓存最快只有1.2ns。
|
||||
|
||||
@@ -409,7 +409,7 @@ Cache的出现是为了解决CPU直接访问内存效率低下问题的。
|
||||
|
||||
每一个线程有自己的工作内存,工作内存只存储该线程对共享变量的副本。线程对变量的所有的操 作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能直接 访问对方工作内存中的变量。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0005.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0005.png">
|
||||
|
||||
Java的线程不能直接在主内存中操作共享变量。而是首先将主内存中的共享变量赋值到自己的工作内存中,再进行操作,操作完成之后,刷回主内存。
|
||||
|
||||
@@ -424,7 +424,7 @@ Java内存模型是一套在多线程读写共享数据时,对共享数据的
|
||||
|
||||
JMM内存模型与CPU硬件内存架构的关系:
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0006.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0006.png">
|
||||
|
||||
工作内存:可能对应CPU寄存器,也可能对应CPU缓存,也可能对应内存。
|
||||
|
||||
@@ -434,9 +434,9 @@ JMM内存模型与CPU硬件内存架构的关系:
|
||||
|
||||
## 再谈可见性
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0007.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0007.png">
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0008.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0008.png">
|
||||
|
||||
1、图中所示是 个双核 CPU 系统架构 ,每个核有自己的控制器和运算器,其中控制器包含一组寄存器和操作控制器,运算器执行算术逻辅运算。每个核都有自己的1级缓存,在有些架构里面还有1个所有 CPU 共享的2级缓存。 那么 Java 内存模型里面的工作内存,就对应这里的 Ll 或者 L2 存或者 CPU 寄存器。
|
||||
|
||||
@@ -452,7 +452,7 @@ JMM内存模型与CPU硬件内存架构的关系:
|
||||
|
||||
为了保证数据交互时数据的正确性,Java内存模型中定义了8种操作来完成这个交互过程,这8种操作本身都是原子性的。虚拟机实现时必须保证下面 提及的每一种操作都是原子的、不可再分的。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0009.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0009.png">
|
||||
|
||||
> (1)lock:作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
|
||||
>
|
||||
@@ -478,7 +478,7 @@ JMM内存模型与CPU硬件内存架构的关系:
|
||||
|
||||
如果没有synchronized,那就是下面这样的
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0010.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0010.png">
|
||||
|
||||
|
||||
|
||||
@@ -589,7 +589,7 @@ volatile不保证原子性,只保证可见性和禁止指令重排
|
||||
|
||||
## CPU术语介绍
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0011.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0011.png">
|
||||
|
||||
|
||||
|
||||
@@ -717,7 +717,7 @@ public class VolatileExample {
|
||||
|
||||
**1、下面是保守策略下,volatile写插入内存屏障后生成的指令序列示意图**
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0012.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0012.png">
|
||||
|
||||
> 图中的StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作已经对任 意处理器可见了。这是因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。这里比较有意思的是,volatile写后面的StoreLoad屏障。此屏障的作用是避免volatile写与 后面可能有的volatile读/写操作重排序。因为编译器常常无法准确判断在一个volatile写的后面 是否需要插入一个StoreLoad屏障(比如,一个volatile写之后方法立即return)。为了保证能正确 实现volatile的内存语义,JMM在采取了保守策略:在每个volatile写的后面,或者在每个volatile 读的前面插入一个StoreLoad屏障。从整体执行效率的角度考虑,JMM最终选择了在每个volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见使用模式是:一个 写线程写volatile变量,多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时, 选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里可以看到JMM在实现上的一个特点:首先确保正确性,然后再去追求执行效率
|
||||
|
||||
@@ -725,7 +725,7 @@ public class VolatileExample {
|
||||
|
||||
**2、下面是在保守策略下,volatile读插入内存屏障后生成的指令序列示意图**
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0013.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0013.png">
|
||||
|
||||
> 图中的LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。 LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。 上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障。
|
||||
|
||||
@@ -752,7 +752,7 @@ class VolatileBarrierExample {
|
||||
|
||||
针对readAndWrite()方法,编译器在生成字节码时可以做如下的优化
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0014.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0014.png">
|
||||
|
||||
注意,最后的StoreLoad屏障不能省略。因为第二个volatile写之后,方法立即return。此时编译器可能无法准确断定后面是否会有volatile读或写,为了安全起见,编译器通常会在这里插入一个StoreLoad屏障。
|
||||
|
||||
@@ -766,7 +766,7 @@ class VolatileBarrierExample {
|
||||
|
||||
X86处理器仅会对写-读操作做重排序。X86不会对读-读、读-写和写-写操作 做重排序,因此在X86处理器中会省略掉这3种操作类型对应的内存屏障。在X86中,JMM仅需在volatile写后面插入一个StoreLoad屏障即可正确实现volatile写-读的内存语义。这意味着在X86处理器中,volatile写的开销比volatile读的开销会大很多(因为执行StoreLoad屏障开销会比较大)。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0015.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0015.png">
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ categories:
|
||||
- 原理
|
||||
keywords: Java并发,原理,源码
|
||||
description: '万字系列长文讲解-Java并发体系-第二阶段,从C++和硬件方面讲解。'
|
||||
cover: 'https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/logo_1.png'
|
||||
cover: 'https://gitee.com/youthlql/randombg/raw/master/logo/Java_concurrency.png'
|
||||
abbrlink: '8210870'
|
||||
date: 2020-10-07 22:10:58
|
||||
---
|
||||
@@ -18,7 +18,7 @@ date: 2020-10-07 22:10:58
|
||||
|
||||
# 可见性设计的硬件
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0016.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0016.png">
|
||||
|
||||
从硬件的级别来考虑一下可见性的问题
|
||||
|
||||
@@ -305,13 +305,13 @@ MESI协议规定了一组消息,就说各个处理器在操作内存数据的
|
||||
|
||||
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0017.jpg">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0017.jpg">
|
||||
|
||||
|
||||
|
||||
## MESI-优化
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0018.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0018.png">
|
||||
|
||||
MESI协议如果每次写数据的时候都要发送invalidate消息等待所有处理器返回ack,然后获取独占锁后才能写数据,那可能就会导致性能很差了,因为这个对共享变量的写操作,实际上在硬件级别变成串行的了。所以为了解决这个问题,硬件层面引入了写缓冲器和无效队列
|
||||
|
||||
@@ -449,7 +449,7 @@ int b = c; //load
|
||||
|
||||
## 相关术语
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0019.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0019.png">
|
||||
|
||||
|
||||
|
||||
@@ -461,7 +461,7 @@ int b = c; //load
|
||||
|
||||
**第一个机制是通过总线锁保证原子性。**如果多个处理器同时对共享变量进行读改写操作 (i++就是经典的读改写操作),那么共享变量就会被多个处理器同时进行操作,这样读改写操 作就不是原子的,操作完之后共享变量的值会和期望的不一致。举个例子,如果i=1,我们进行 两次i++操作,我们期望的结果是3,但是有可能结果是2,如图所示。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0020.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0020.png">
|
||||
|
||||
原因可能是多个处理器同时从各自的缓存中读取变量i,分别进行加1操作,然后分别写入 系统内存中。那么,想要保证读改写共享变量的操作是原子的,就必须保证CPU1读改写共享 变量的时候,CPU2不能操作缓存了该共享变量内存地址的缓存。
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ categories:
|
||||
- 原理
|
||||
keywords: Java并发,原理,源码
|
||||
description: '万字系列长文讲解-Java并发体系-第二阶段,从C++和硬件方面讲解。'
|
||||
cover: 'https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/logo_1.png'
|
||||
cover: 'https://gitee.com/youthlql/randombg/raw/master/logo/Java_concurrency.png'
|
||||
abbrlink: 113a3931
|
||||
date: 2020-10-08 22:10:58
|
||||
---
|
||||
@@ -357,17 +357,17 @@ monitorexit释放锁。 monitorexit插入在方法结束处和异常处,JVM保
|
||||
|
||||
术语参考: http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html 在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。如下图所示:
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0021.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0021.png">
|
||||
|
||||
## 对象头
|
||||
|
||||
当一个线程尝试访问synchronized修饰的代码块时,它首先要获得锁,那么这个锁到底存在哪里呢?是 存在锁对象的对象头中的。 HotSpot采用instanceOopDesc和arrayOopDesc来描述对象头,arrayOopDesc对象用来描述数组类型。instanceOopDesc的定义的在Hotspot源码的 instanceOop.hpp 文件中,另外,arrayOopDesc 的定义对应 arrayOop.hpp
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0022.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0022.png">
|
||||
|
||||
从instanceOopDesc代码中可以看到 instanceOopDesc继承自oopDesc,oopDesc的定义载Hotspot 源码中的 oop.hpp 文件中。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0023.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0023.png">
|
||||
|
||||
- 在普通实例对象中,oopDesc的定义包含两个成员,分别是 _mark 和 _metadata
|
||||
|
||||
@@ -383,21 +383,21 @@ monitorexit释放锁。 monitorexit插入在方法结束处和异常处,JVM保
|
||||
|
||||
Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、 线程持有的锁、偏向线程ID、偏向时间戳等等,占用内存大小与虚拟机位长一致。Mark Word对应的类 型是 markOop 。源码位于 markOop.hpp 中。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0024.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0024.png">
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0025.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0025.png">
|
||||
|
||||
在64位虚拟机下,Mark Word是64bit大小的,其存储结构如下:
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0026.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0026.png">
|
||||
|
||||
在32位虚拟机下,Mark Word是32bit大小的,其存储结构如下:
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0027.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0027.png">
|
||||
|
||||
再加一个图对比一下,有一丁点的补充
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0028.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0028.png">
|
||||
|
||||
|
||||
|
||||
@@ -447,7 +447,7 @@ Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)
|
||||
|
||||
线程在执行同步块之前,JVM会先在当前的线程的栈帧中创建一个`Lock Record`,其包括一个用于存储对象头中的 `mark word`(官方称之为`Displaced Mark Word`)以及一个指向对象的指针。下图右边的部分就是一个`Lock Record`。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0029.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0029.png">
|
||||
|
||||
|
||||
|
||||
@@ -571,7 +571,7 @@ synchronized(obj){
|
||||
}
|
||||
```
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0030.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0030.png">
|
||||
|
||||
## 轻量级锁什么时候升级为重量级锁?
|
||||
|
||||
@@ -608,7 +608,7 @@ synchronized(obj){
|
||||
|
||||
- 重量级锁的状态下,对象的`mark word`为指向一个堆中monitor对象的指针。一个monitor对象包括这么几个关键字段:cxq(下图中的ContentionList),EntryList ,WaitSet,owner。其中cxq ,EntryList ,WaitSet都是由ObjectWaiter的链表结构,owner指向持有锁的线程。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0031.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0031.png">
|
||||
|
||||
在HotSpot虚拟机中,monitor是由ObjectMonitor实现的。其源码是用c++来实现的,位于HotSpot虚 拟机源码ObjectMonitor.hpp文件中(src/share/vm/runtime/objectMonitor.hpp)。ObjectMonitor主 要数据结构如下:
|
||||
|
||||
@@ -661,7 +661,7 @@ ObjectMonitor() {
|
||||
|
||||
1、执行monitorenter时,会调用InterpreterRuntime.cpp (位于:src/share/vm/interpreter/interpreterRuntime.cpp) 的 InterpreterRuntime::monitorenter函 数。具体代码可参见HotSpot源码。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0032.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0032.png">
|
||||
|
||||
|
||||
|
||||
@@ -1147,7 +1147,7 @@ if (TryLock(Self) > 0) break ;
|
||||
|
||||
可以看到ObjectMonitor的函数调用中会涉及到Atomic::cmpxchg_ptr,Atomic::inc_ptr等内核函数, 执行同步代码块,没有竞争到锁的对象会park()被挂起,竞争到锁的线程会unpark()唤醒。这个时候就 会存在操作系统用户态和内核态的转换,这种切换会消耗大量的系统资源。所以synchronized是Java语 言中是一个重量级(Heavyweight)的操作。 用户态和和内核态是什么东西呢?要想了解用户态和内核态还需要先了解一下Linux系统的体系架构:
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0033.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Second_stage/0033.png">
|
||||
|
||||
从上图可以看出,Linux操作系统的体系架构分为:用户空间(应用程序的活动空间)和内核。 内核:本质上可以理解为一种软件,控制计算机的硬件资源,并提供上层应用程序运行的环境。 用户空间:上层应用程序活动的空间。应用程序的执行必须依托于内核提供的资源,包括CPU资源、存 储资源、I/O资源等。 系统调用:为了使上层应用能够访问到这些资源,内核必须为上层应用提供访问的接口:即系统调用。
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ categories:
|
||||
- 原理
|
||||
keywords: Java并发,AQS源码
|
||||
description: '万字系列长文讲解-Java并发体系-第四阶段-AQS源码解读-[1]。'
|
||||
cover: 'https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/logo_1.png'
|
||||
cover: 'https://gitee.com/youthlql/randombg/raw/master/logo/Java_concurrency.png'
|
||||
abbrlink: 92c4503d
|
||||
date: 2020-10-26 17:59:42
|
||||
---
|
||||
@@ -410,11 +410,11 @@ Process finished with exit code 0
|
||||
|
||||
**技术翻译:**是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石, 通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量`state`表示持有锁的状态。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0001.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0001.png">
|
||||
|
||||
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0011.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0011.png">
|
||||
|
||||
AbstractOwnableSynchronizer
|
||||
AbstractQueuedLongSynchronizer
|
||||
@@ -426,7 +426,7 @@ AbstractQueuedSynchronizer
|
||||
|
||||
AQS是一个抽象的父类,可以将其理解为一个框架。基于AQS这个框架,我们可以实现多种同步器,比如下方图中的几个Java内置的同步器。同时我们也可以基于AQS框架实现我们自己的同步器以满足不同的业务场景需求。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0002.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0002.png">
|
||||
|
||||
|
||||
|
||||
@@ -434,7 +434,7 @@ AQS是一个抽象的父类,可以将其理解为一个框架。基于AQS这
|
||||
|
||||
加锁会导致阻塞:有阻塞就需要排队,实现排队必然需要有某种形式的队列来进行管理
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0003.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0003.png">
|
||||
|
||||
1、抢到资源的线程直接使用办理业务,抢占不到资源的线程的必然涉及一种**排队等候机制**,抢占资源失败的线程继续去等待(类似办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。
|
||||
|
||||
@@ -515,7 +515,7 @@ Node 的数据结构其实也挺简单的,就是 thread + waitStatus + pre + n
|
||||
|
||||
## AQS队列基本结构
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0004.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0004.png">
|
||||
|
||||
注意排队队列,不包括head(也就是后文要说的哨兵节点)。
|
||||
|
||||
@@ -583,7 +583,7 @@ public class AQSDemo {
|
||||
|
||||
以这样的一个实际例子说明。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0005.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0005.png">
|
||||
|
||||
|
||||
|
||||
@@ -765,7 +765,7 @@ public class AQSDemo {
|
||||
|
||||
2、C在if逻辑里准备入队,进行相应设置后,变成下面这样。
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0006.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0006.png">
|
||||
|
||||
|
||||
|
||||
@@ -831,7 +831,7 @@ public class AQSDemo {
|
||||
|
||||
此时队列变成了下面的样子:
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0007.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0007.png">
|
||||
|
||||
3、然后if结束之后,继续空的for循环,B线程开始了第二轮循环。
|
||||
|
||||
@@ -843,11 +843,11 @@ public class AQSDemo {
|
||||
|
||||
2、`node.prev = t`,进入if之后,让B节点的prev指针指向t,然后`compareAndSetTail(t, node)`设置尾节点
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0008.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0008.png">
|
||||
|
||||
3、CAS设置尾节点成功之后,执行if里的逻辑
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0009.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0009.png">
|
||||
|
||||
|
||||
|
||||
@@ -1197,7 +1197,7 @@ protected final void setExclusiveOwnerThread(Thread thread) {
|
||||
|
||||
|
||||
|
||||
<img src="https://unpkg.zhimg.com/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0010.png">
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.8/Java_concurrency/Source_code/Fourth_stage/0010.png">
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user