zoukankan      html  css  js  c++  java
  • 【Java并发基础】管程简介

    前言

    在Java 1.5之前,Java语言提供的唯一并发语言就是管程,Java 1.5之后提供的SDK并发包也是以管程为基础的。除了Java之外,C/C++、C#等高级语言也都是支持管程的。

    那么什么是管程呢?
    见名知意,是指管理共享变量以及对共享变量操作的过程,让它们支持并发。翻译成Java领域的语言,就是管理类的状态变量,让这个类是线程安全的。

    synchronized关键字和wait()、notify()、notifyAll()这三个方法是Java中实现管程技术的组成部分。记得学习操作系统时,在线程一块还有信号量机制,管程在功能上和信号量及PV操作类似,属于一种进程同步互斥工具。Java选择管程来实现并发主要还是因为实现管程比较容易。

    管程对应的英文是Monitor,直译为“监视器”,而操作系统领域一般翻译为“管程”。

    在管程的发展史上,先后出现过三种不同的管程模型,分别是Hasen模型、Hoare模型和MESA模型。现在正在广泛使用的是MESA模型。下面我们便介绍MESA模型。

    MESA模型

    管程中引入了条件变量的概念,而且每个条件变量都对应有一个等待队列。条件变量和等待队列的作用是解决线程之间的同步问题。

    我们来看一个例子来理解这个模型。多个线程对一个共享队列进行操作。

    假设线程T1要执行出队操作,但是这个操作要执行成功的前提是队列不能为空。这个队列不能为空就是管程里的条件变量。若是线程T1进入管程后发现队列是空的,那它就需要在“队列不空”这个条件变量的等待队列中等待
    通过调用wait()实现。若是用对象A代表“队列不空”这个条件,那么线程T1需要调用A.wait(),来将自己阻塞。
    在线程T1进入条件变量的等待队列后,是允许其他线程进入管程的。

    再假设之后另外一个线程T2执行了入队操作,入队操作成功之后,“队列不空”这个条件对于线程T1来说已经满足了,此时线程T2要通知线程T1,告诉它调用需要的条件已经满足了。
    那么线程T2怎么通知线程T1?线程T2调用A.notify()来通知A等待队列中的一个线程,此时这个线程里面只有T1,所以notify唤醒的就是线程T1,如果当这个条件变量的等待队列不止T1一个线程,我们就需要使用notifyAll()。
    当线程T1得到通知后,会从等待队列中出来,重新进入到入口等待队列中

    使用代码说明就如下:(代码来自参考[1])
    注意,await()和前面的wait()的语义是一样的;signal()和前面的notify()语义是一样的(没有提到的signalAll()notifyAll()语义也是一样的)。

    public class BlockedQueue<T>{
        final Lock lock = new ReentrantLock();
        // 条件变量:队列不满  
        final Condition notFull = lock.newCondition();
        // 条件变量:队列不空  
        final Condition notEmpty = lock.newCondition();
    
        // 入队
        void enq(T x) {
            lock.lock();
            try {
                while (队列已满){
                    // 等待队列不满
                    notFull.await();
                }  
                // 省略入队操作...
                // 入队后, 通知可出队
                notEmpty.signal();
            }finally {
                lock.unlock();
            }
        }
        // 出队
        void deq(){
            lock.lock();
            try {
                while (队列已空){
                    // 等待队列不空
                    notEmpty.await();
                }
                // 省略出队操作...
                // 出队后,通知可入队
                notFull.signal();
            }finally {
                lock.unlock();
            }  
        }
    }
    

    wait()的正确使用姿势

    对于MESA管程来说,有一个编程范式:

    while(条件不满足) {
      wait();
    }
    

    我们在前面介绍等待-通知机制时就提到过这种范式。这个范式可以解决“条件曾将满足过”这个问题。唤醒的时间和获取到锁继续执行的时间是不一致的,被唤醒的线程再次执行时可能条件又不满足了,所以循环检验条件

    MESA模型的wait()方法还有一个超时参数,为了避免线程进入等待队列永久阻塞

    notify()和notifyAll()分别何时使用

    满足以下三个条件时,可以使用notify(),其余情况尽量使用notifyAll():

    1. 所有等待线程拥有相同的等待条件;
    2. 所有等待线程被唤醒后,执行相同的操作;
    3. 只需要唤醒一个线程。

    三种管程模型在通知线程上的区别

    Hasen模型、Hoare模型和MESA模型的一个核心区别是当条件满足后,如何通知相关线程

    管程要求同一时刻只允许一个线程执行那当线程T2的操作使得线程T1等待的条件满足时,T1和T2究竟谁可以执行呢

    1. 在Hasen模型里,要求notify()放在代码的最后,这样T2通知完T1后,T2就结束了,然后T1再执行这样就可以保证同一时刻只有一个线程执行。
    2. 在Hoare模型里面,T2通知完T1后,T2阻塞,T1马上执行;等T1执行完,再唤醒t2。比起Hasen模型,T2多了一次阻塞唤醒操作。
    3. 在MESA管程里,T2通知完T1后,T2还是会接着执行,T1并不立即执行,仅仅是从条件变量的等待队列进入到入口等待队列中但是T1再次执行时,可能条件又不满足了,所以需要循环防方式检验条件变量)。这样的好处是:notify()代码不用放到代码的最后,T2也没有多余的阻塞唤醒操作。

    Java语言的内置管程synchronized

    Java 参考了 MESA 模型,语言内置的管程(synchronized)对 MESA 模型进行了精简。MESA 模型中,条件变量可以有多个,Java 语言内置的管程里只有一个条件变量。模型如下图所示。(图来自参考[1])

    Java 内置的管程方案(synchronized)使用简单,synchronized 关键字修饰的代码块,在编译期会自动生成相关加锁和解锁的代码,但是仅支持一个条件变量;而 Java SDK 并发包实现的管程支持多个条件变量,不过并发包里的锁,需要我们自己进行加锁和解锁操作。

    小结

    开始本来打算不写这篇学习笔记的,但是思考了一下,Java并发实现本就是源于操作系统中的管程,既然要好好介绍Java并发那么它的来源也应该要好好介绍一下。在学习一个知识的时候,其背后的理论也要好好掌握。

    参考:
    [1]极客时间专栏王宝令《Java并发编程实战》

  • 相关阅读:
    SDUT 2143 图结构练习——最短路径 SPFA模板,方便以后用。。 Anti
    SDUT ACM 1002 Biorhythms 中国剩余定理 Anti
    nyist OJ 119 士兵杀敌(三) RMQ问题 Anti
    SDUT ACM 2157 Greatest Number Anti
    SDUT ACM 2622 最短路径 二维SPFA启蒙题。。 Anti
    二叉索引树 区间信息的维护与查询 Anti
    SDUT ACM 2600 子节点计数 Anti
    UVA 1428 Ping pong 二叉索引树标准用法 Anti
    2010圣诞Google首页效果
    Object
  • 原文地址:https://www.cnblogs.com/myworld7/p/12238796.html
Copyright © 2011-2022 走看看