欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

管程:并发编程的基石

程序员文章站 2022-05-05 09:41:36
...

点击上方菜鸟飞呀飞或者扫描下方二维码,即可关注微信公众号。

管程:并发编程的基石

本文主要参考了极客时间上王宝令老师的《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();
        }
    }

}
  • 通过对上面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;
 }
  • 在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并发编程实战》这门课程,下方是课程的二维码链接。

管程:并发编程的基石