管程:并发编程的基石

文章目录
1. 定义
2. MESA模型
2.1 互斥
2.2 同步
3. synchronized
4. Lock与Condition
5. 总结
本文主要参考了极客时间上王宝令老师的《Java并发编程实战》,里面大部分内容来自这门课程,本文中的几张图也是参考课程里面的图画的。个人觉得这门课程比较基础,相对而言比较简单,老师讲解得也十分清晰,适合并发编程的入门,有需要的朋友可以点击文末的二维码连接去了解。

1. 定义
Monitor在英语中直译是监视器的意思,但是在操作系统中通常被翻译为管程,是用来实现并发的一种技术,它解决了并发编程中的两大核心问题:互斥与同步。所以管程的定义是:用来管理共享变量以及对共享变量操作的过程。
历史上出现过三种管程模型,MESA模型、Hasen模型、Hoare模型。而在Java中,管程的实现是根据MESA模型实现的。
2. MESA模型
管程实现了并发编程领域的两大核心问题:互斥与同步,那么MESA模型是如何来实现互斥与同步的呢?

2.1 互斥
管程通过对共享变量以及对操作共享变量方法的进行了封装,在同一时刻,只有一个线程进入到管程中,这样就保证了只有一个线程来操作共享变量,实现了互斥的功能。

如上图中,管程X对共享变量count,以及对操作共享变量count的两个方法递增:increment()和递减:decrement()进行了封装,要想访问共享变量count,只能通过increment()和decrement()。由于管程保证了同时只允许一个线程进入,所以保证了increment()和decrement()的互斥性。
2.2 同步
如下图的管程模型图中,方框代表管程对共享变量以及操作共享变量方法的封装,在入口处有一个等待队列,当有多个线程试图进入管程时,管程只允许一个线程进入,其他的线程进入到等待队列中。
在管程中引入了条件变量的概念,每个条件变量都对应一个等待队列。如图中的条件变量A和B分别对应一个等待队列。

条件变量和等待队列的作用就是为了实现线程之间的同步问题。可以结合下面的例子理解。
假设有如下场景,对于共享变量count,初始值为0,我们需要对它进行递增或者递减操作,但是在进行操作时,需要满足如下条件,进行递增时,count的值不能大于等于10,进行递减时,count的值需要大于0。
假设线程T1要对共享变量进行递减操作,由于此时count值为0,所以不能进行递减操作,这个时候就应该让线程T1进行等待。去哪儿等待呢?去条件变量对应的等待队列里面进行等待。所以此时需要调用count>0这个条件变量对象的wait()方法,这样线程T1就进入到count>0这个条件变量的等待队列中等待。
再假设线程T2要对共享变量进行递增操作,由于此时count=0,满足count值不能大于等于10这个条件,所以T2执行递增操作,操作之后count变为1,那么此时对于条件变量count>0对于线程T1来说已经成立了,所以这个时候T2需要通知T1条件满足了。那么如何通知呢?通过调用count>0这个条件变量对象的notify()或者notifyAll()方法。通知完成后,T1线程会从条件变量的等待队列中出来,但是此时T1不会立马执行,而是需要重新进入到管程入口处的等待队列中。
可以结合如下代码理解。在代码中使用了java.util.concurrent包下的类Lock和Condition,await()方法和wait()方法作用是一样的,signalAll()和notifyAll()作用是一样的。
public class Count {

    volatile int count = 0;

    final Lock lock = new ReentrantLock();

    // 小于10条件变量
    final Condition lessThanTen = lock.newCondition();

    // 大于0条件变量
    final Condition moreThanZero = lock.newCondition();

    void increment(){
        lock.lock();
        try{
            while(!count小于10){
                // 小于10这个条件变量不满足,调用await()方法进入到条件变量的等待队列中
                lessThanTen.await();
            }
            // 执行递增操作
            // 递增完成以后,通知大于0这个条件等待队列中的线程
            moreThanZero.signalAll();
        }finally {
          lock.unlock();
        }
    }

    void decrement(){
        lock.lock();
        try{
            while(!count大于0){
                // 大于0这个条件变量不满足,调用await()方法进入到条件变量的等待队列中
                moreThanZero.await();
            }
            // 执行递减操作

            // 执行递减操作以后,通知小于10这个条件等待队列中的线程
            lessThanTen.signalAll();
        }finally{
            lock.unlock();
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
通过对上面MESA管程模型的介绍,到这里相信你对管程有了一定的认识。那管程在Java中到底是如何实现的呢?
3. synchronized
Java语言中为我们提供了synchronized关键字来实现锁。synchronized关键词实现的锁是隐式锁,为什么称之为隐式锁呢?是因为在编译器编译Java文件时,当碰到synchronized时,会自动加上加锁指令和解锁指令,即monitorenter和monitorexit,这一步对于开发人员来说是透明的,所以说它是隐式锁。它的底层实现就是通过管程实现的,前面我们介绍的管程模型中,可以支持多个条件变量,但是synchronized的管程实现只支持一个条件变量。它的管程示意图如下:

那么在虚拟机中,是如何实现管程的呢?那就是ObjectMonitor这个对象了。在openJDK的源码中,有这样两个源文件:objectMonitor.cpp和objectMonitor.hpp。前者的文件中实现了锁的获取、释放等方法,后者定义了前者需要的头文件。在objectMonitor.hpp文件中定义了这样一个结构:
ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    // 条件等待队列
    _WaitSet      = NULL;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    // 入口等待队列
    _EntryList    = NULL ;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在ObjectMonitor这个结构中,有两个重要的属性:_WaitSet和_EntryList,这两个属性分别对应管程中条件等待队列和入口等待队列。
4. Lock与Condition
Lock和Condition是java.util.concurrent包下的类,与synchronized关键字实现锁的原理不一样,Lock是在Java层面实现的,而synchronized是通过JVM虚拟机实现的锁。

Lock和Condition也是通过管程模型来实现锁的。其中Lock是是用来实现互斥的,Condition是用来实现同步的。
与synchronized不同的是,Lock与Condition实现的管程,支持多条件变量,而synchronized的管程实现只支持单个条件变量。
5. 总结
本文主要介绍了管程的模型以及管程是如何来实现并发的,
本文结合Java中synchronized和Lock介绍了管程的实现。
本文的内容更偏于个人的学习笔记,很多地方讲解得不是很详细,如果想了解更加详细的,可以去看看极客时间上《Java并发编程实战》这门课程,下方是课程的二维码链接。
————————————————
版权声明:本文为CSDN博主「天堂2013」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_34436819/article/details/101913269

 

 

 

深层次探讨mutex与semaphore之间的区别(上)

 

semaphore和mutex的区别?

 https://www.zhihu.com/question/47704079

 

java的同步底层就是使用mutex来实现的管程,最后一级重量级锁 使用的是object monitor

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章