并发编程学习(8) —— 管程

什么是管程

在之前我提到过管程这一概念,那管程到底是什么呢?管程其实指的是管理共享变量以及管理共享变量的操作过程

在并发领域中,最主要解决两个问题,第一个问题就是互斥,即同一时间只对一个共享变量继续操作。第二个问题就是同步,线程间如何通信、协调。

管程解决互斥

管程解决互斥问题相对简单,把共享变量以及共享变量的操作都封装在一个类中,如下图:


管程互斥模型图
当线程A和线程B需要获取共享变量count时,就需要调用get和set方法,而get和set方法则保证互斥性,保证每次只能有一个线程访问。

管程解决同步

对于同步问题,管程的实现方法有点复杂,我这里展示一下从https://time.geekbang.org/column/article/86089中摘下的MESA管程模型图,框里的表示被封装的变量以及它的操作方法,框表示封装:
在这里插入图片描述
我这里结合下医院就诊流程来描述该图:

  1. 病人先拿到号,并在医生门口等待叫号。(线程中则是在图中的入口等待队列等待获取锁)
  2. 等了许久,病人终于被医生叫到号,然后进入诊室准备就诊。(线程获取锁成功)
  3. 医生由于病人缺少体检报告而不能准确地查出病情,这时病人就被医生要求去做体检,这时病人需要去体检中心体检,病人就需要去体检中心领取体检单。(条件变量A可以想象成病人是否有体检单,如果没有,则去体检中心等待领取体检单。条件B也是同样的道理,缺少什么,就去哪条等待队列中等待)。
  4. 当病人拿到单子后就返回到医生诊室门口等待。(即是图上的入口等待队列)

有一点需要注意的是,当线程因条件不足而在等待队列中等待的时候就会释放锁,其它线程能够获取到锁,就像病人去拿体检报告的时候医生能够诊断其它病人。

用线程来描述的话就是:有两个线程A和B,线程A和线程B在入口等待队列中等待锁的获取,线程A加锁成功,但由于条件变量A不满足,则在条件变量A的等待队列中等待,若是条件变量B不满足,则在条件变量B的等待队列中等待(每个条件变量都有属于自己的等待队列),直到条件满足位置。当线程A等待的时候,会释放锁,线程B加锁成功。当调节变量A满足时,会通知等待队列中的线程,这里是线程A,线程A被告知条件满足后,会重新回到入口等待队列中等待。

整个流程就是这样,值得一提的是,线程A被告知条件满足指的是这一个时刻是满足的,并不能保证通知后下一次的校验还是满足。

说完理论,就要用代码来实现下,前面说到的等待-通知在JAVA中的方法就是wait()、notifyAll()、notify()。那么我们来弄一个阻塞队列,当队列满的时候,不允许入队,当出队操作,若队列为空,则不允许出队,直到队列不为空。如果出队成功,就通知入队中的等待队列。若入队成功,则通知出队的等待队列。代码实现如下:

public class BlockedQueue<T>{

    final Lock lock = new ReentrantLock();

    // 队列不空
    final Condition notEmpty = lock.newCondition();

    // 队列不满
    final Condition notFull = lock.newCondition();

    void inEq(T x){
        lock.lock();
        try{
            while(/*"队列已满*/){
                // 等待队列不满
                notFull.await();
            }
            // 队列不满,则通知等待队列中的线程
            notFull.signalAll();
        }finally {
            lock.unlock();
        }
    }

    void outEq(){
        lock.lock();
        try{
            while(/*队列为空*/){
                // 等待队列不为空
                notEmpty.await();
            }
            // 队列不为空,则通知等待队列中的线程
            notEmpty.signalAll();
        }finally {
            lock.unlock();
        }
    }
}

里面的await()语义其实和wait()一样,signalAll即notifyAll,signal即notify。这时候可能有人会有疑问,为什么不用signal而是用signalAll?因为signal只会随机通知一个线程,这会可能造成线程“饿死”,也就是说有些线程永远不会被通知到,而使用signalAll则所有的线程都被通知,因此如果不是特别需要,就尽量用notifyAll或signalAll。

猜你喜欢

转载自blog.csdn.net/weixin_38168947/article/details/89309037
今日推荐