zoukankan      html  css  js  c++  java
  • 管程|| 同步与互斥的实现策略

    • 什么是管程?

    它是monitor在操作系统领域中的间接翻译,也可以称它为监视器。那管程的具体作用是什么呢?:它是描述并实现对共享变量的管理与操作 ,使其在多线程环境下能正确执行的一个管理策略。基于这个定义,我们也可以把管程当作一个临界资源区的管理策略,管程的实现可以是多样的。下面我们就来介绍一下前人已经总结出来的实现模型。

    • 管程的策略实现模型

    在历史中,管程模型有三种,它们分别是:Hasen 模型、Hoare 模型和 MESA 模型。在介绍模型之前:我们需要先了解清楚相关术语及数据结构。

    enterQueue:管程入口队列,当线程在申请进入管程中发现管程已被占用,那会被放入该队列并进入阻塞状态。

    varQueue:条件变量等待队列,当线程执行过程中,条件变量不符合要求,线程被阻塞时,线程会被放入该队列。

    signalQueue:唤醒线程队列,当管程中的线程主动唤醒在varQueue中的队列时,它会被放入该队列。它比enterQueue的线程有优先权。(这是Hasen 模型、Hoare 模型特有的队列)

    condition variables:条件变量,它是存在于管程中的,条件变量通常是由程序赋予意义,程序通过判断条件变量是否符合所需条件来调用同步与互斥操作。

    阻塞操作:例如我们java中用到的wait()、await()方法。

    唤醒操作:java中通常指notify()、signal()方法。阻塞和唤醒操作可以认为是一种同步与互斥方法,不同线程通过这两种方法进行通信协作。

    由于Java中使用的是MESA模型,下面我们只对MESA模型做详细介绍,对于另外两个只做简单总结。我们先从Java的关键字synchronized介绍。由于使用的是MESA模型,它是没有signalQueue的,而synchronized也仅支持一个条件变量。当然,JDK中还可以通过Lock实现多个条件变量,简易synchronized中的MESA模型如下所示:

    上面的执行过程如下:

    1. 多个线程进入到enterQueue,JVM保证只有一个线程能够进入到管程内部,synchronize中进入管程的线程是随机的。
    2. 通过条件变量判断当前线程是否能执操作,若不能执行,则跳到第3步。若能执行,则跳到第4步。
    3. 条件变量调用wait()方法,当前线程进入阻塞状态,将其放入varQueue,等待其它线程唤醒,跳到步骤1。
    4. 执行相应的操作,执行完毕后调用notify()或者notifyAll()方法,唤醒在varQueue中阻塞的一个或全部线程。
    5. 所有在varQueue中的线程会被放入enterQueue中,再次执行步骤1。

    经过上述步骤,我们有一个思考,那么就是当前执行的线程唤醒在varQueue的线程的时候,这两个线程间的执行顺序是什么?这也是不同模型所要了解的重点。

    MESA模型:条件变量队列中的阻塞线程被唤醒后,不会立即执行而是放入到enterQueue队列,等待下一次JVM的选择运行。而正在运行的线程会继续执行,直到程序执行完毕。

    Hasen 模型:条件变量队列中的阻塞线程被唤醒后,会在当前线程执行完成后立即运行刚被唤醒的阻塞线程,它的优先级是比enterQueue队列中的线程优先级高的。

    Hoare 模型:条件变量队列中的阻塞线程被唤醒后,当前线程会立即中断,并运行刚刚被唤醒的阻塞线程,等阻塞线程完成再回来运行。

    (上述的Hasen、Hoare模型的模式从总结上来说是这样的,但还是与实际有点不同,但是由于它不是Java用到的模型,我们也不去深究。如果有感兴趣的可以到这个网站去了解:https://en.wikipedia.org/wiki/Monitor_(synchronization)#Blocking_condition_variables

    下面,我们可以通过一个简单的阻塞队列的实现方式来更近一步了解管程是怎么进行同步与互斥的。

     1 package com.study.unit3.section2;
     2 
     3 import java.util.LinkedList;
     4 import java.util.List;
     5 import java.util.concurrent.locks.Condition;
     6 import java.util.concurrent.locks.ReentrantLock;
     7 
     8 /**
     9  * @author HILL
    10  * @version V1.0
    11  * @date 2019/7/26
    12  **/
    13 public class BlockQueue {
    14 
    15     private List<Integer> blockList = new LinkedList<Integer>();
    16     private final int limit;
    17     //lock实现互斥
    18     private ReentrantLock lock = new ReentrantLock();
    19     //条件变量实现同步通信
    20     private Condition putCondition = lock.newCondition();
    21     private Condition getCondition = lock.newCondition();
    22 
    23     public BlockQueue(int limit) {
    24         this.limit = limit;
    25     }
    26 
    27     public Integer get() {
    28         lock.lock();
    29         try {
    30             while (blockList.size() <= 0) {
    31                 //队列中没有元素,阻塞想要获取元素的线程
    32                 getCondition.await();
    33             }
    34         } catch (InterruptedException e) {
    35             e.printStackTrace();
    36         } finally {
    37             lock.unlock();
    38         }
    39         //通知线程可以放入元素
    40         putCondition.signalAll();
    41         return blockList.remove(0);
    42     }
    43 
    44     public void set(Integer num) {
    45         lock.lock();
    46         try {
    47             while (blockList.size() == limit) {
    48                 putCondition.await();
    49             }
    50             blockList.add(num);
    51             getCondition.signalAll();
    52         } catch (Exception e) {
    53             e.printStackTrace();
    54         } finally {
    55             lock.unlock();
    56         }
    57 
    58     }
    59 }
    •  Java中wait()方法的正确使用

    Java的管程是基于MESA模型的,那它的特点是什么?那就是此刻满足执行条件,被激活的线程不一定能马上执行,那就有可能存在一个问题。 下一次线程进入时条件已经不满足了,那线程应该是不能执行的。所以,我们要在判断条件的地方使用while判断。至于详细的原因,我们可以通过代码来说明。

    1      if(blockList.size()<=0){
    2       //线程不满足条件,进入休眠。
    3        getCondition.await();   //当下一次线程被激活的时候,线程会从这一步开始往下执行。
    4 
    5     }

    可以看到,当线程再次获得执行权利的时候,是不用从方法口重新进入,而是从await()这个方法往下面走。因为MESA模型的策略,当线程再次获得执行权利的时候,它的运行条件blockList.size()<=0不一定满足,如果让线程往下面执行,会发生意想不到的错误。所以,正确的方式应该是这样的:

    1      
    2             while (blockList.size() <= 0) {
    3                 //线程不满足条件,进入休眠。
    4                 getCondition.await();   //当下一次线程被激活的时候,由于while的原因,会再一次判断运行结果。
    5             }
    • 总结

    对于JAVA的管程来说,具体的表现形式就是synchronize和lock接口。只有了解JAVA管程的具体策略才能了解并发程序中的线程的执行策略,当出错的时候才能更加准确的分析出BUG。

  • 相关阅读:
    Blob
    MySQL This function has none of DETERMINISTIC, NO SQL...错误1418 的原因分析及解决方法 (转)
    事务--存储过程
    JDBC-Mysql-编译预处理(占位符)
    socket
    GUI---深度复制
    串行化--深度复制
    RESTful理解
    django中文和时区的配置
    redis-server报错
  • 原文地址:https://www.cnblogs.com/hill1126/p/11245782.html
Copyright © 2011-2022 走看看