Skip to content

Commit 075593b

Browse files
committed
[docs update]java并发部分内容重构完善
1 parent f535bc7 commit 075593b

14 files changed

+63
-31
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ Dubbo 是一款国产的 RPC 框架,由阿里开源。相关阅读:
342342
消息队列在分布式系统中主要是为了解耦和削峰。相关阅读: [消息队列常见问题总结](docs/high-performance/message-queue/message-queue.md)
343343

344344
1. **RabbitMQ** : [RabbitMQ 基础知识总结](docs/high-performance/message-queue/rabbitmq-intro.md)[RabbitMQ 常见面试题](docs/high-performance/message-queue/rabbitmq-questions.md)
345-
2. **RocketMQ** : [RocketMQ 基础知识总结](docs/high-performance/message-queue/rocketmq-intro)[RocketMQ 常见面试题总结](docs/high-performance/message-queue/rocketmq-questions.md)
345+
2. **RocketMQ** : [RocketMQ 基础知识总结](docs/high-performance/message-queue/rocketmq-intro.md)[RocketMQ 常见面试题总结](docs/high-performance/message-queue/rocketmq-questions.md)
346346
3. **Kafka**[Kafka 常见问题总结](docs/high-performance/message-queue/kafka-questions-01.md)
347347

348348
### 读写分离&分库分表

docs/java/basis/java-basic-questions-03.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ head:
6161
### try-catch-finally 如何使用?
6262

6363
- `try`块 : 用于捕获异常。其后可接零个或多个 `catch` 块,如果没有 `catch` 块,则必须跟一个 `finally` 块。
64-
- \*`catch`块 : 用于处理 try 捕获到的异常。
64+
- `catch`块 : 用于处理 try 捕获到的异常。
6565
- `finally` 块 : 无论是否捕获或处理异常,`finally` 块里的语句都会被执行。当在 `try` 块或 `catch` 块中遇到 `return` 语句时,`finally` 语句块将在方法返回之前被执行。
6666

6767
代码示例:
Binary file not shown.
-73.3 KB
Binary file not shown.
-46.7 KB
Binary file not shown.

docs/java/concurrent/java-concurrent-questions-01.md

+52-24
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ head:
2020

2121
在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。
2222

23-
如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe 文件的运行)。
23+
如下图所示,在 Windows 中通过查看任务管理器的方式,我们就可以清楚看到 Windows 当前运行的进程(`.exe` 文件的运行)。
2424

2525
![进程示例图片-Windows](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/进程示例图片-Windows.png)
2626

2727
### 何为线程?
2828

2929
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的******方法区**资源,但每个线程有自己的**程序计数器****虚拟机栈****本地方法栈**,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
3030

31-
Java 程序天生就是多线程程序,我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程,代码如下。
31+
Java 程序天生就是多线程程序,我们可以通过 JMX 来看看一个普通的 Java 程序有哪些线程,代码如下。
3232

3333
```java
3434
public class MultiThread {
@@ -59,13 +59,13 @@ public class MultiThread {
5959

6060
## 请简要描述线程与进程的关系,区别及优缺点?
6161

62-
**从 JVM 角度说进程和线程之间的关系**
62+
从 JVM 角度说进程和线程之间的关系
6363

6464
### 图解进程和线程的关系
6565

6666
下图是 Java 内存区域,通过下图我们从 JVM 的角度来说一下线程和进程之间的关系。
6767

68-
![](../jvm/pictures/java内存区域/Java运行时数据区域JDK1.8.png)
68+
![Java 运行时数据区域(JDK1.8 之后)](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/java/jvm/java-runtime-data-areas-jdk1.8.png)
6969

7070
从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的******方法区 (JDK1.8 之后的元空间)**资源,但是每个线程有自己的**程序计数器****虚拟机栈****本地方法栈**
7171

@@ -97,11 +97,18 @@ public class MultiThread {
9797

9898
堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
9999

100-
## 说说并发与并行的区别?
100+
## 并发与并行的区别
101101

102102
- **并发**:两个及两个以上的作业在同一 **时间段** 内执行。
103103
- **并行**:两个及两个以上的作业在同一 **时刻** 执行。
104104

105+
最关键的点是:是否是 **同时** 执行。
106+
107+
## 同步和异步的区别
108+
109+
- **同步** : 发出一个调用之后,在没有得到结果之前, 该调用就不可以返回,一直等待。
110+
- **异步** :调用在发出之后,不用等待返回结果,该调用直接返回。
111+
105112
## 为什么要使用多线程呢?
106113

107114
先从总体上来说:
@@ -120,33 +127,42 @@ public class MultiThread {
120127

121128
## 说说线程的生命周期和状态?
122129

123-
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态(图源《Java 并发编程艺术》4.1.4 节)。
130+
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态
124131

125-
![Java 线程的状态 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81.png)
132+
- NEW: 初始状态,线程被创建出来但没有被调用 `start()`
133+
- RUNNABLE: 运行状态,线程被调用了 `start()`等待运行的状态。
134+
- BLOCKED :阻塞状态,需要等待锁释放。
135+
- WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
136+
- TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
137+
- TERMINATED:终止状态,表示该线程已经运行完毕。
126138

127-
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源 [issue#736](https://github.com/Snailclimb/JavaGuide/issues/736)):
139+
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。
128140

129-
![](./images/interview-questions/java-life-cycle.png)
141+
Java 线程状态变迁图(图源:[挑错 |《Java 并发编程的艺术》中关于线程状态的三处错误](https://mp.weixin.qq.com/s/UOrXql_LhOD8dhTq_EPI0w)):
130142

131-
> 订正(来自[issue736](https://github.com/Snailclimb/JavaGuide/issues/736)):原图中 wait 到 runnable 状态的转换中,`join`实际上是`Thread`类的方法,但这里写成了`Object`
143+
![Java 线程状态变迁图](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/java/concurrent/640.png)
132144

133145
由上图可以看出:线程创建之后它将处于 **NEW(新建)** 状态,调用 `start()` 方法后开始运行,线程这时候处于 **READY(可运行)** 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 **RUNNING(运行)** 状态。
134146

135147
> 在操作系统层面,线程有 READY 和 RUNNING 状态;而在 JVM 层面,只能看到 RUNNABLE 状态(图源:[HowToDoInJava](https://howtodoinJava.com/ "HowToDoInJava")[Java Thread Life Cycle and Thread States](https://howtodoinJava.com/Java/multi-threading/Java-thread-life-cycle-and-thread-states/ "Java Thread Life Cycle and Thread States")),所以 Java 系统一般将这两个状态统称为 **RUNNABLE(运行中)** 状态 。
136148
>
137-
> **为什么 JVM 没有区分这两种状态呢?** (摘自:[java线程运行怎么有第六种状态? - Dawell的回答](https://www.zhihu.com/question/56494969/answer/154053599)现在的<b>时分</b>(time-sharing)<b>多任务</b>(multi-task)操作系统架构通常都是用所谓的“<b>时间分片</b>(time quantum or time slice)”方式进行<b>抢占式</b>(preemptive)轮转调度(round-robin式)。这个时间分片通常是很小的,一个线程一次最多只能在 CPU 上运行比如 10-20ms 的时间(此时处于 running 状态),也即大概只有 0.01 秒这一量级,时间片用后就要被切换下来放入调度队列的末尾等待再次调度。(也即回到 ready 状态)。线程切换的如此之快,区分这两种状态就没什么意义了。
149+
> **为什么 JVM 没有区分这两种状态呢?** (摘自:[Java 线程运行怎么有第六种状态? - Dawell 的回答](https://www.zhihu.com/question/56494969/answer/154053599)现在的时分(time-sharing)多任务(multi-task)操作系统架构通常都是用所谓的“时间分片(time quantum or time slice)”方式进行抢占式(preemptive)轮转调度(round-robin 式)。这个时间分片通常是很小的,一个线程一次最多只能在 CPU 上运行比如 10-20ms 的时间(此时处于 running 状态),也即大概只有 0.01 秒这一量级,时间片用后就要被切换下来放入调度队列的末尾等待再次调度。(也即回到 ready 状态)。线程切换的如此之快,区分这两种状态就没什么意义了。
138150
139151
![RUNNABLE-VS-RUNNING](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RUNNABLE-VS-RUNNING.png)
140152

141-
当线程执行 `wait()`方法之后,线程进入 **WAITING(等待)** 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 **TIMED_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long millis)`方法可以将 Java 线程置于 TIMED_WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED(阻塞)** 状态。线程在执行 Runnable 的`run()`方法之后将会进入到 **TERMINATED(终止)** 状态。
153+
- 当线程执行 `wait()`方法之后,线程进入 **WAITING(等待)** 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态。
154+
- **TIMED_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long millis)`方法可以将线程置于 TIMED_WAITING 状态。当超时时间结束后,线程将会返回到 RUNNABLE 状态。
155+
- 当线程进入 `synchronized` 方法/块或者调用 `wait` 后(被 `notify`)重新进入 `synchronized` 方法/块,但是锁被其它线程占有,这个时候线程就会进入 **BLOCKED(阻塞)** 状态。
156+
- 线程在执行完了 `run()`方法之后将会进入到 **TERMINATED(终止)** 状态。
142157

143-
相关阅读:[挑错 |《Java 并发编程的艺术》中关于线程状态的三处错误](https://mp.weixin.qq.com/s/UOrXql_LhOD8dhTq_EPI0w)
158+
相关阅读:[线程的几种状态你真的了解么?](https://mp.weixin.qq.com/s/R5MrTsWvk9McFSQ7bS0W2w)
144159

145160
## 什么是上下文切换?
146161

147162
线程在执行过程中会有自己的运行条件和状态(也称上下文),比如上文所说到过的程序计数器,栈信息等。当出现如下情况的时候,线程会从占用 CPU 状态中退出。
163+
148164
- 主动让出 CPU,比如调用了 `sleep()`, `wait()` 等。
149-
- 时间片用完,因为操作系统要防止一个线程或者进程长时间占用CPU导致其他线程或者进程饿死
165+
- 时间片用完,因为操作系统要防止一个线程或者进程长时间占用 CPU 导致其他线程或者进程饿死
150166
- 调用了阻塞类型的系统中断,比如请求 IO,线程被阻塞。
151167
- 被终止或结束运行
152168

@@ -235,12 +251,12 @@ Thread[线程 2,5,main]waiting get resource1
235251

236252
避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。
237253

238-
> **安全状态** 指的是系统能够按照某种线程推进顺序(P1、P2、P3.....Pn)来为每个线程分配所需资源,直到满足每个线程对资源的最大需求,使每个线程都可顺利完成。称<P1、P2、P3.....Pn>序列为安全序列。
254+
> **安全状态** 指的是系统能够按照某种线程推进顺序(P1、P2、P3.....Pn)来为每个线程分配所需资源,直到满足每个线程对资源的最大需求,使每个线程都可顺利完成。称 `<P1、P2、P3.....Pn>` 序列为安全序列。
239255
240256
我们对线程 2 的代码修改成下面这样就不会产生死锁了。
241257

242258
```java
243-
new Thread(() -> {
259+
new Thread(() -> {
244260
synchronized (resource1) {
245261
System.out.println(Thread.currentThread() + "get resource1");
246262
try {
@@ -256,7 +272,7 @@ Thread[线程 2,5,main]waiting get resource1
256272
}, "线程 2").start();
257273
```
258274

259-
Output
275+
输出:
260276

261277
```
262278
Thread[线程 1,5,main]get resource1
@@ -273,17 +289,29 @@ Process finished with exit code 0
273289

274290
线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。
275291

276-
## 说说 sleep() 方法和 wait() 方法区别和共同点?
292+
## sleep() 方法和 wait() 方法对比
293+
294+
**共同点** :两者都可以暂停线程的执行。
295+
296+
**区别**
297+
298+
- **`sleep()` 方法没有释放锁,而 `wait()` 方法释放了锁**
299+
- `wait()` 通常被用于线程间交互/通信,`sleep()`通常被用于暂停执行。
300+
- `wait()` 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 `notify()`或者 `notifyAll()` 方法。`sleep()`方法执行完成后,线程会自动苏醒,或者也可以使用 `wait(long timeout)` 超时后线程会自动苏醒。
301+
- `sleep()``Thread` 类的静态本地方法,`wait()` 则是 `Object` 类的本地方法。为什么这样设计呢?
302+
303+
## 为什么 wait() 方法不定义在 Thread 中?
304+
305+
`wait()` 是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。每个对象(`Object`)都拥有对象锁,既然要释放当前线程占有的对象锁并让其进入 WAITING 状态,自然是要操作对应的对象(`Object`)而非当前的线程(`Thread`)。
306+
307+
类似的问题:**为什么 `sleep()` 方法定义在 `Thread` 中?**
277308

278-
- 两者最主要的区别在于:**`sleep()` 方法没有释放锁,而 `wait()` 方法释放了锁**
279-
- 两者都可以暂停线程的执行。
280-
- `wait()` 通常被用于线程间交互/通信,`sleep() `通常被用于暂停执行。
281-
- `wait()` 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 `notify() `或者 `notifyAll()` 方法。`sleep() `方法执行完成后,线程会自动苏醒。或者可以使用 `wait(long timeout)` 超时后线程会自动苏醒。
309+
因为 `sleep()` 是让当前线程暂停执行,不涉及到对象类,也不需要获得对象锁。
282310

283-
## 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法
311+
## 可以直接调用 Thread 类的 run 方法吗
284312

285313
这是另一个非常经典的 Java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!
286314

287-
new 一个 Thread,线程进入了新建状态。调用 `start()`方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 `start()` 会执行线程的相应准备工作,然后自动执行 `run()` 方法的内容,这是真正的多线程工作。 但是,直接执行 `run()` 方法,会把 `run()` 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
315+
new 一个 `Thread`,线程进入了新建状态。调用 `start()`方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 `start()` 会执行线程的相应准备工作,然后自动执行 `run()` 方法的内容,这是真正的多线程工作。 但是,直接执行 `run()` 方法,会把 `run()` 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
288316

289317
**总结: 调用 `start()` 方法方可启动线程并使线程进入就绪状态,直接执行 `run()` 方法的话不会以多线程的方式执行。**

docs/java/concurrent/java-concurrent-questions-02.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ JMM(Java 内存模型)相关的问题比较多,也比较重要,于是我
2222

2323
在 Java 中,`volatile` 关键字可以保证变量的可见性,如果我们将变量声明为 **`volatile`** ,这就指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。
2424

25-
![JMM(Java 内存模型)](./images/jmm/jmm.png)
25+
![JMM(Java 内存模型)](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/java/concurrent/jmm.png)
26+
27+
![JMM(Java 内存模型)强制在主存中进行读取](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/java/concurrent/jmm2.png)
2628

2729
`volatile` 关键字其实并非是 Java 语言特有的,在 C 语言里也有,它最原始的意义就是禁用 CPU 缓存。如果我们将一个变量使用 `volatile` 修饰,这就指示 编译器,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。
2830

docs/java/concurrent/jmm.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ JMM 说白了就是定义了一些规范来解决这些问题,开发发者可
8888

8989
Java 内存模型的抽象示意图如下:
9090

91-
![JMM(Java 内存模型)](./images/jmm/jmm.png)
91+
![JMM(Java 内存模型)](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/java/concurrent/jmm.png)
9292

9393
从上图来看,线程 1 与线程 2 之间如果要进行通信的话,必须要经历下面 2 个步骤:
9494

docs/java/jvm/memory-area.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成
2525

2626
**JDK 1.8 之前**
2727

28-
![](./pictures/java内存区域/JVM运行时数据区域.png)
28+
![Java 运行时数据区域(JDK1.8 之前)](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/java/jvm/java-runtime-data-areas-jdk1.7.png)
2929

30-
**JDK 1.8**
30+
**JDK 1.8 之后**
3131

32-
![](./pictures/java内存区域/Java运行时数据区域JDK1.8.png)
32+
![Java 运行时数据区域(JDK1.8 之后)](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/java/jvm/java-runtime-data-areas-jdk1.8.png)
3333

3434
**线程私有的:**
3535

Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)