parent
4ad2359949
commit
526d0d2255
@ -0,0 +1,71 @@ |
|||||||
|
--- |
||||||
|
Java 并发编程 |
||||||
|
--- |
||||||
|
|
||||||
|
#### 目录 |
||||||
|
|
||||||
|
#### 管程:并发编程的万能钥匙 |
||||||
|
|
||||||
|
管程,对应的英文是 Monitor,也称监视器。 |
||||||
|
|
||||||
|
所谓管程,指的是管理共享变量以及对共享变量的操作过程,让它们支持并发。翻译为 Java 领域的语言,就是管理类的成员变量和成员方法,让这个类是线程安全的。那么管程是如何管的呢? |
||||||
|
|
||||||
|
##### MESA 模型 |
||||||
|
|
||||||
|
在管程的发展史上,先后出现过三种不同的管程模型,分别是:Hasen 模型、Hoare 模型和 MESA 模型。其中,现在广泛应用的是 MESA 模型,并且 Java 管程的实现参考的也是 MESA 模型。 |
||||||
|
|
||||||
|
在并发编程领域,有两大核心问题:一个是互斥,即同一时刻只允许一个线程访问共享资源;另一个是同步,即线程之间如何通信、协作。这两大问题,管程都是能够解决的。 |
||||||
|
|
||||||
|
管程解决互斥问题的思路很简单,就是将共享变量及其对共享变量的操作统一封装起来。在下图中,管程 X 把对共享变量的操作封装了起来,也就保证了只有一个线程能够进入管程。这也是和 OOP 思想高度契合的。 |
||||||
|
|
||||||
|
![管程_互斥](https://i.loli.net/2019/03/18/5c8eff280ea48.png) |
||||||
|
|
||||||
|
那管程是如何解决线程间同步的问题呢? |
||||||
|
|
||||||
|
下面给出一个管程模型示意图,它详细描述了 MESA 模型主要组成部分。 |
||||||
|
|
||||||
|
在管程模型里,共享变量和对共享变量的操作是被封装起来的,图中最外层的框就代表封装的意思。框的上面只有一个入口,并且在入口旁边还有一个入口等待队列。当多个线程同时试图进入管程内部时,只允许一个线程进入,其他线程则在入口等待队列中等待。 |
||||||
|
|
||||||
|
管程里还引入了条件变量的概念,而且每个条件变量都对应有一个等待队列。 |
||||||
|
|
||||||
|
![](https://i.loli.net/2019/03/18/5c8f02ca403c0.png) |
||||||
|
|
||||||
|
条件变量和等待队列的作用就是解决线程同步的问题。 |
||||||
|
|
||||||
|
以上面入对出队的例子讲解: |
||||||
|
|
||||||
|
假设有个线程 T1 执行出队操作,如果当前队列为空,此时线程 T1 就需要在 "队列不空" 这个条件变量的等待队列中等待,即执行 A.wait() 来实现。 |
||||||
|
|
||||||
|
如果又有另外一个线程 T2 执行入队操作,入队操作执行成功之后,"队列不空" 这个条件对于线程 T1 来说就已经满足了,此时线程 T2 要通知 T1,告诉它需要的条件已经满足了,即执行 A.notify() 来通知 A 等待队列中的一个线程。当线程 T1 得到通知后,会从等待队列里面出来,但是出来之后不是马上执行,而是重新进入到入口队列等待队列里面。至于 notifyAll 方法,则是通知等待队列中所有的线程。 |
||||||
|
|
||||||
|
##### wait() 的正确姿势 |
||||||
|
|
||||||
|
对于 MESA 模型来说,有一个编程范式,就是需要在一个 while 循环里面调用 wait()。这个是 MESA 管程特有的。 |
||||||
|
|
||||||
|
```java |
||||||
|
while(条件不满足){ |
||||||
|
wait(); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Hasen 模型、Hoare 模型和 MESA 模型的一个核心区别就是当条件满足后,如何通知相关线程。管程要求同一时刻只允许一个线程执行,那当线程 T2 的操作使得线程 T1 等待的条件满足时,T1 和 T2 究竟谁可以执行呢? |
||||||
|
|
||||||
|
1. Hasen 模型里面,要求 notify() 方法代码最后,这样 T2 通知完 T1 后,T2 就结束了,然后 T1 再执行,这样1同一时刻只有一个线程执行。 |
||||||
|
2. Hoare 模型里面,T2 通知完 T1 后,T2 阻塞,T1 马上执行;等 T1 执行完,在唤醒 T2,也能保证同一时刻只有一个线程执行。但是相比 Hasen 模型,T2 多了一次阻塞唤醒操作。 |
||||||
|
3. MESA 模型里面,T2 通知完 T1 后,T2 还是会接着执行,T1 并不立即执行,仅仅是从条件变量的等待队列进入入口等待队列里面。这样做的好处是 notify() 不用放到代码的最后,T2 也没有多余的阻塞唤醒操作。但是也有个副作用,就是当 T1 再次执行的时候,可能曾经满足的条件,现在已经不满足了,所以需要以循环的方式检验条件变量。 |
||||||
|
|
||||||
|
##### notify() 何时可以使用 |
||||||
|
|
||||||
|
除非经过深思熟虑,否则尽量使用 noyifyAll()。那么什么时候使用 notify 呢?需要满足以下三个条件: |
||||||
|
|
||||||
|
1. 所有等待线程拥有相同的等待条件 |
||||||
|
2. 所有等待线程被唤醒后,执行相同的操作 |
||||||
|
3. 只需要唤醒一个线程 |
||||||
|
|
||||||
|
##### 总结 |
||||||
|
|
||||||
|
Java 参考了 MESA 模型,语言内置的管程(synchronized)对 MESA 模型进行精简。MESA 模型中,条件变量可以有多个,Java 语言内置的管程里只有一个条件变量,具体如下: |
||||||
|
|
||||||
|
![](https://i.loli.net/2019/03/18/5c8f0eba877b4.png) |
||||||
|
|
||||||
|
Java 内置的管程方案(synchronized)使用简单,synchronized 关键字修饰的代码块,在编译期会自动生成相关加锁和解锁的代码,但是仅支持一个条件变量;而 Java SDK 并发包实现的管程支持多个条件变量,不过并发包里的锁,需要开发人员自己进行加锁和解锁操作。 |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 130 KiB |
After Width: | Height: | Size: 83 KiB |
After Width: | Height: | Size: 188 KiB |
Loading…
Reference in new issue