Skip to content

Commit b19cf9b

Browse files
committed
docs: 更新文档中的图片链接
1 parent 248fa12 commit b19cf9b

File tree

317 files changed

+2073
-2194
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

317 files changed

+2073
-2194
lines changed

docs/00.笔记/Java/极客时间教程-Java并发编程实战笔记一.md

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ permalink: /pages/ed3f0c85/
1414

1515
## 学习攻略 如何才能学好并发编程?
1616

17-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408261435639.png)
17+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/8a2ebe85df1d43c49126f1972c13bdf8.png)
1818

1919
## 开篇词 你为什么需要学习并发编程?
2020

@@ -42,11 +42,11 @@ CPU、内存、I/O 设备三者的速度存在很大差异。为了合理利用
4242

4343
例如在下面的图中,线程 A 和线程 B 都是操作同一个 CPU 里面的缓存,所以线程 A 更新了变量 V 的值,那么线程 B 之后再访问变量 V,得到的一定是 V 的最新值(线程 A 写过的值)。
4444

45-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408261442765.png)
45+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/730d6af712c347298f67cd9e99622ec8.png)
4646

4747
对于**多核**,当多个线程在不同的 CPU 上执行时,这些线程操作的是不同的 CPU 缓存。这时两个线程对于变量的操作就不具备可见性了。
4848

49-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408261444744.png)
49+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/1e69175b321848e48b3b325657558a24.png)
5050

5151
【示例】计数器的并发安全问题示例
5252

@@ -85,11 +85,11 @@ public class Test {
8585

8686
操作系统允许某个进程执行一小段时间,例如 50 毫秒,过了 50 毫秒操作系统就会重新选择一个进程来执行(我们称为“任务切换”),这个 50 毫秒称为“**时间片**”。现代的操作系统都基于更轻量的线程来调度,现在我们提到的“任务切换”都是指“线程切换”。
8787

88-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408261450096.png)
88+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/681b590828ed41a3af84bff62303cb79.png)
8989

9090
Java 的并发也是基于任务切换。Java 中,即使是一条语句,也可能需要执行多条 CPU 指令。**一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性**
9191

92-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408292035170.png)
92+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/8736467ba93f4ad7aeff928ae09fc4a9.png)
9393

9494
### 编译优化带来的有序性问题
9595

@@ -126,7 +126,7 @@ public class Singleton {
126126

127127
优化后会导致什么问题呢?我们假设线程 A 先执行 getInstance() 方法,当执行完指令 2 时恰好发生了线程切换,切换到了线程 B 上;如果此时线程 B 也执行 getInstance() 方法,那么线程 B 在执行第一个判断时会发现 `instance != null` ,所以直接返回 instance,而此时的 instance 是没有初始化过的,如果我们这个时候访问 instance 的成员变量就可能触发空指针异常。
128128

129-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408261457434.png)
129+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/8c3b002320a24895849dbd9149c19ccf.png)
130130

131131
## Java 内存模型:看 Java 如何解决可见性和有序性问题
132132

@@ -153,7 +153,7 @@ public class Singleton {
153153

154154
举例来说,long 型变量是 64 位,在 32 位 CPU 上执行写操作会被拆分成两次写操作(写高 32 位和写低 32 位,如下图所示)。
155155

156-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408261524478.png)
156+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/7d94ecc28af04e51bb5212c0fc6b447a.png)
157157

158158
在多核场景下,同一时刻,有可能有两个线程同时在执行,一个线程执行在 CPU-1 上,一个线程执行在 CPU-2 上,此时禁止 CPU 中断,只能保证 CPU 上的线程连续执行,并不能保证同一时刻只有一个线程执行,如果这两个线程同时写 long 型变量高 32 位的话,那就有可能出现我们开头提及的诡异 Bug 了。
159159

@@ -163,13 +163,13 @@ public class Singleton {
163163

164164
一段需要互斥执行的代码称为**临界区**。线程在进入临界区之前,首先尝试加锁 lock(),如果成功,则进入临界区,此时称这个线程持有锁;否则就等待,直到持有锁的线程解锁;持有锁的线程执行完临界区的代码后,执行解锁 unlock()。
165165

166-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408292036259.png)
166+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/7a89e2585de64852993ac524d189c38b.png)
167167

168168
### 改进后的锁模型
169169

170170
首先,我们要把临界区要保护的资源标注出来,如图中临界区里增加了一个元素:受保护的资源 R;其次,我们要保护资源 R 就得为它创建一把锁 LR;最后,针对这把锁 LR,我们还需在进出临界区时添上加锁操作和解锁操作。
171171

172-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408292036343.png)
172+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/d6ee6fae6e2440088c191acd2a1045b3.png)
173173

174174
### Java 语言提供的锁技术:synchronized
175175

@@ -215,7 +215,7 @@ class SafeCalc {
215215
}
216216
```
217217

218-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408261541380.png)
218+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/615fa48f45d94efc99f760ce1101dd8c.png)
219219

220220
### 锁和受保护资源的关系
221221

@@ -235,7 +235,7 @@ class SafeCalc {
235235
}
236236
```
237237

238-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408261545832.png)
238+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/a124365b348e48459bd3e4aa4fbc32a6.png)
239239

240240
【示例】synchronized 实现并发安全的计数器错误示例
241241

@@ -332,7 +332,7 @@ class Account {
332332

333333
synchronized 可以保护 this 对象持有的资源,但不能保护 target 对象持有的资源。
334334

335-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408261603806.png)
335+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/d15aaa05fed54a8ebc8669baacce5bdb.png)
336336

337337
### 使用锁的正确姿势
338338

@@ -377,7 +377,7 @@ class Account {
377377
}
378378
```
379379

380-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408261610209.png)
380+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/6aae2001fefa41768f0ab05a62031593.png)
381381

382382
## 一不小心就死锁了,怎么办?
383383

@@ -404,7 +404,7 @@ class Account {
404404
}
405405
```
406406

407-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408261612406.png)
407+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/9832934fc044414a9b998632a9f41d32.png)
408408

409409
### 如何预防死锁
410410

@@ -534,7 +534,7 @@ class Account {
534534

535535
Java 中,等待-通知机制有多种实现方式,比如 Java 语言内置的 synchronized 配合 wait()、notify()、notifyAll() 这三个方法就能轻松实现。
536536

537-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408292037649.png)
537+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/0aedca99c182428bba9b6b87ff2a5ced.png)
538538

539539
wait()、notify()、notifyAll() 方法操作的等待队列是互斥锁的等待队列,所以如果 synchronized 锁定的是 this,那么对应的一定是 this.wait()、this.notify()、this.notifyAll();如果 synchronized 锁定的是 target,那么对应的一定是 target.wait()、target.notify()、target.notifyAll() 。而且 wait()、notify()、notifyAll() 这三个方法能够被调用的前提是已经获取了相应的互斥锁,所以我们会发现 wait()、notify()、notifyAll() 都是在 synchronized{}内部被调用的。如果在 synchronized{}外部调用,或者锁定的 this,而用 target.wait() 调用的话,JVM 会抛出一个运行时异常:`java.lang.IllegalMonitorStateException`。
540540

@@ -643,21 +643,21 @@ Java 参考了 MESA 模型,语言内置的管程(synchronized)对 MESA 模
643643

644644
将共享变量及其对共享变量的操作统一封装起来。在下图中,管程 X 将共享变量 queue 这个队列和相关的操作入队 enq()、出队 deq() 都封装起来了;线程 A 和线程 B 如果想访问共享变量 queue,只能通过调用管程提供的 enq()、deq() 方法来实现;enq()、deq() 保证互斥性,只允许一个线程进入管程。
645645

646-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408261940150.png)
646+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/064d0729db5a407e80d588f32149c697.png)
647647

648648
管程是如何解决线程间的**同步**问题的:
649649

650650
在管程模型里,共享变量和对共享变量的操作是被封装起来的,图中最外层的框就代表封装的意思。框的上面只有一个入口,并且在入口旁边还有一个入口等待队列。当多个线程同时试图进入管程内部时,只允许一个线程进入,其他线程则在入口等待队列中等待。管程里还引入了条件变量的概念,而且**每个条件变量都对应有一个等待队列**,如下图,条件变量 A 和条件变量 B 分别都有自己的等待队列。
651651

652-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408270725745.png)
652+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/98f980ca00954813bb288ab9a991ab3a.png)
653653

654654
## Java 线程(上):Java 线程的生命周期
655655

656656
### 通用的线程生命周期
657657

658658
通用的线程生命周期:**初始状态、可运行状态、运行状态、休眠状态****终止状态**
659659

660-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408270729535.png)
660+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/0925452aead94f1ba6d4e0eef210b729.png)
661661

662662
### Java 中线程的生命周期
663663

@@ -670,7 +670,7 @@ Java 中线程共有六种状态:
670670
5. TIMED_WAITING(有时限等待)
671671
6. TERMINATED(终止状态)
672672

673-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408270731084.png)
673+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/d57e12e28c934036bd8c593511c545f8.png)
674674

675675
## Java 线程(中):创建多少线程才是合适的?
676676

@@ -701,29 +701,29 @@ Java 中线程共有六种状态:
701701

702702
### 方法是如何被执行的
703703

704-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408270751420.png)
704+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/723a39fae7854d69aee28e0440d60a63.png)
705705

706706
CPU 去哪里找到调用方法的参数和返回地址?
707707

708708
**通过 CPU 的堆栈寄存器**CPU 支持一种栈结构,先入后出。因为这个栈是和方法调用相关的,因此经常被称为**调用栈**
709709

710710
例如,有三个方法 ABC,他们的调用关系是 A->B->CA 调用 BB 调用 C),在运行时,会构建出下面这样的调用栈。每个方法在调用栈里都有自己的独立空间,称为**栈帧**,每个栈帧里都有对应方法需要的参数和返回地址。当调用方法时,会创建新的栈帧,并压入调用栈;当方法返回时,对应的栈帧就会被自动弹出。也就是说,**栈帧和方法是同生共死的**
711711

712-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408270753265.png)
712+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/04da5269c8de49708cd349b67c80a4b3.png)
713713

714714
### 局部变量存哪里?
715715

716716
局部变量的作用域是方法内部,也就是说当方法执行完,局部变量就没用了,局部变量应该和方法同生共死。此时你应该会想到调用栈的栈帧,调用栈的栈帧就是和方法同生共死的,所以局部变量放到调用栈里那儿是相当的合理。事实上,的确是这样的,**局部变量就是放到了调用栈里**。于是调用栈的结构就变成了下图这样。
717717

718-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408270755942.png)
718+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/ba943afdb47b41e388b7e86a26fce8a6.png)
719719

720720
### 调用栈与线程
721721

722722
那调用栈和线程之间是什么关系呢?
723723

724724
答案是:**每个线程都有自己独立的调用栈**
725725

726-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408270756092.png)
726+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/e023266be34041fa9440fe953cb25ef1.png)
727727

728728
因为每个线程都有自己的调用栈,局部变量保存在线程各自的调用栈里面,不会共享,所以自然也就没有并发问题。再次重申一遍:没有共享,就没有伤害。
729729

@@ -765,8 +765,8 @@ CPU 去哪里找到调用方法的参数和返回地址?
765765

766766
管程,是 Java 并发编程技术的基础,是解决并发问题的万能钥匙。并发编程里两大核心问题——互斥和同步,都是可以由管程来解决的。
767767

768-
![](https://raw.githubusercontent.com/dunwu/images/master/snap/202408270805546.png)
768+
![](https://raw.githubusercontent.com/dunwu/images/master/archive/2024/08/92dfec54fa6b4735b8f15ae463c96d3f.png)
769769

770770
## 参考资料
771771

772-
- [极客时间教程 - Java 并发编程实战](https://time.geekbang.org/column/intro/100023901)
772+
- [极客时间教程 - Java 并发编程实战](https://time.geekbang.org/column/intro/100023901)

0 commit comments

Comments
 (0)