部分内容来自以下博客:
https://www.cnblogs.com/xidongyu/p/10891303.html
https://www.cnblogs.com/kkkkkk/p/5543799.html
1 定义
1.1 信号
信号是一种抽象数据类型,包括一个整型变量和两个原子操作P和V,其原子性由操作系统保证,这个整型变量只能通过P操作和V操作改变。
P操作:P意味着信号量值减1,减完之后如果信号量值小于0,则说明资源不够用的,把进程加入等待队列。
V操作:V意味着信号量值加1,加完之后如果信号量值小于等于0,则说明等待队列里有进程,那么唤醒一个等待进程。
P操作可能阻塞,V操作不会阻塞。
1.2 信号量
信号量是被保护的整型变量。在初始化完成后,只能通过PV操作修改,操作系统保证了PV操作的原子性。
使用信号量实现临界区的互斥访问时,必须成对使用P操作和V操作。
开发代码比较困难,PV在不同的线程里配对容易写错,而且必须先检查资源信号量的值,再进入临界区,否则所有线程都不能进入临界区。
1.3 管程
管程是一种用于多线程互斥访问共享资源的程序结构,其采用了面向对象方法,简化了线程间的同步控制。
管程是为了解决信号量在临界区的PV操作上的配对的麻烦,把配对的PV操作集中在一起,生成的一种并发编程方法。
管程包括一个锁以及0或者多个条件变量。
管程与临界区不同的是,在管程中的线程可以临时放弃管程的互斥访问,让其他线程进入到管程中来。而临界区中的线程只能在线程退出临界区时,才可以放弃对临界区的访问。
2 模型
2.1 MASE
在管程的发展史上,先后出现过三种不同的管程模型,分别是:Hasen模型、Hoare模型和MESA模型。其中,现在广泛应用的是MESA模型,并且Java管程的实现参考的也是MESA模型。
在并发编程领域,有两大核心问题:一个是互斥,即同一时刻只允许一个线程访问共享资源;另一个是同步,即线程之间如何通信、协作。这两大问题,管程都是能够解决的。
2.2 互斥操作
在管程模型里,共享变量和对共享变量的操作是被封装起来的。当多个线程同时试图进入管程内部时,只允许一个线程进入,其他线程则在入口等待队列中等待。
2.3 同步操作
解决线程同步问题需要使用条件变量和等待队列。当进入管程的线程发现条件变量不满足时就会进入等待队列阻塞,等条件变量满足时就会从等待队列中唤醒继续执行。
2.4 Hasen、Hoare、MESA的区别
Hasen模型、Hoare模型、MESA模型的一个核心区别就是当条件满足后,如何通知相关线程。
Hasen模型里面,要求notify()放在代码的最后,这样T2通知完T1后,T2就结束了,然后T1再执行,这样就能保证同一时刻只有一个线程执行。
Hoare模型里面,T2通知完T1后,T2阻塞,T1马上执行,等T1执行完,再唤醒T2,也能保证同一时刻只有一个线程执行。但是相比Hasen模型,T2多了一次阻塞唤醒操作。
MESA模型里面,T2通知完T1后,T2还是会接着执行,T1并不立即执行,仅仅是从条件变量的等待队列进到入口等待队列里面。这样做的好处是notify()不用放到代码的最后,T2也没有多余的阻塞唤醒操作。但是也有个副作用,就是当T1再次执行的时候,可能曾经满足的条件,现在已经不满足了,所以需要以循环方式检验条件变量。