Monitor(管程/监视器)详解

[复制链接]
发表于 2022-9-18 07:40:11 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
说明
  Monitor,直译为“监视器”,而操作系统领域一般翻译为“管程”。管程是指管理共享变量以及对共享变量操作的过程,让它们支持并发。在Java 1.5之前,Java语言提供的唯一并发语言就是管程,Java 1.5之后提供的SDK并发包也是以管程为基础的。除了Java之外,C/C++、C#等高级语言也都是支持管程的。synchronized关键字和wait()、notify()、notifyAll()这三个方法是Java中实现管程技术的组成部分。
 
MESA模型分析
  在管程的发展史上,先后出现过三种不同的管程模型,分别是Hasen模型、Hoare模型和 MESA模型。现在正在广泛使用的是MESA模型。下面我们便介绍MESA模型:
 
 
 
  管程中引入了条件变量的概念,而且每个条件变量都对应有一个等待队列。条件变量和等待 队列的作用是解决线程之间的同步问题。
  分析作用:
     入口等待队列:试图要获取锁的线程都必须要进入到这个队列,只有在这个队列里面才能获取锁,而且每次都是获取到锁才会出队。
     条件变量等待队列:这个是为已获得锁的成员进入等待阻塞准备的,当需要等待条件满足时,为了更好的利用CPU,让线程进入等待阻塞,而什么时候再次获得锁,也就是当等待的条件满足了,就会从这个队列中出去,进入到入口等待队列中,再次获取锁。
 
wait()的正确使用姿势
  对于MESA管程来说,有一个编程范式:
  1. while(条件不满足) {
  2. wait();
  3. }
复制代码
  唤醒的时间和获取到锁继续执行的时间是不一致的,被唤醒的线程再次执行时可能条件又不满足了,所以循环检验条件。MESA模型的wait()方法还有一个超时参数,为了避免线程进入等待 队列永久阻塞。
 
notify()和notifyAll()分别何时使用
  满足以下三个条件时,可以使用notify(),其余情况尽量使用notifyAll():


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

  要知道notify()是随机唤醒一个,而notifyAll()则是唤醒全部。如果是要唤醒特定的线程,最好用notifyAll() + while(条件不满足)来保证指定线程会被唤醒。
 
实际案例:Java语言的内置管程synchronized
    Java 参考了 MESA 模型,语言内置的管程(synchronized)对 MESA 模型进行了精简。MESA模型中,条件变量可以有多个,Java 语言内置的管程里只有一个条件变量。模型如下图所示:

 
 
 
    Monitor机制在Java中的实现
      说明
        java.lang.Object 类定义了 wait(),notify(),notifyAll() 方法,这些方法的具体实现,依赖于 ObjectMonitor 实现,这是 JVM 内部基于 C++ 实现的一套机制。
        所谓ObjectMonitor ,是独立的对象监视器,其中的_object便是用于存储synchronized (lock)中的lock。
        ObjectMonitor其主要数据结构如下(hotspot源码ObjectMonitor.hpp):
  1. ObjectMonitor() {
  2.     _header = NULL; //对象头 markOop
  3.     _count = 0;
  4.     _waiters = 0,
  5.     _recursions = 0; // 锁的重入次数
  6.     _object = NULL; //存储锁对象
  7.     _owner = NULL; // 标识拥有该monitor的线程(当前获取锁的线程)
  8.     _WaitSet = NULL; // 等待线程(调用wait)组成的双向循环链表,_WaitSet是第一个节点
  9.     _WaitSetLock = 0;
  10.     _Responsible = NULL ;
  11.     _succ = NULL ;
  12.     _cxq = NULL ; //多线程竞争锁会先存到这个单向链表中 (FILO栈结构)
  13.     FreeNext = NULL ;
  14.     _EntryList = NULL ; //存放在进入或重新进入时被阻塞(blocked)的线程 (也是存竞争锁失败的线程)
  15.     _SpinFreq = 0;
  16.     _SpinClock = 0;
  17.     OwnerIsThread = 0;
  18.     _previous_owner_tid = 0;
  19. }
复制代码
      图示流程:

 
      说明
        在获取锁时,是将当前线程插入到cxq的头部,而释放锁时,默认策略(QMode=0)是:如果EntryList为空,则将cxq中的元素按原有顺序插入到EntryList,并唤醒第一个线程,也就是当EntryList为空时,是后来的线程先获取锁。_EntryList不为空,直接从_EntryList中唤醒线程。
      
      示例演示:
        1.情况1,三个线程ABC,分别去获取锁,顺序为A,B,C,如果A业务时间较长,则BC都应该进入到_cxq中(FILO栈结构)【C,B】,由于_EntryList为空,则将cxq中的元素按原有顺序插入到EntryList【B,C】,此时C先获取锁。结果为:
  1. A get lock
  2. A release lock
  3. C get lock
  4. C release lock
  5. B get lock
  6. B release lock
复制代码
 
        2.情况2,三个线程ABC,分别去获取锁,顺序为A,B,C,如果A业务时间较短,进入等待状态,进入_WaitSet中等待,则B进入时_cxq和_EntryList为空,B直接获取锁,执行业务时间较长,且C进入到_cxq中,而A也从_WaitSet中满足条件进入到了_EntryList中,当B释放锁时,应该在_EntryList中的A先获取锁,当_EntryList为空时,将_cxq中的C转入到_EntryList,等A释放后,C才能获取锁。结果为:
  1. A get lock
  2. B get lock
  3. B release lock
  4. A release lock
  5. C get lock
  6. C release lock
复制代码
 
        3.示例代码展示:
  1. public class SyncQModeDemo {
  2.     public static void main(String[] args) throws InterruptedException {
  3.         SyncQModeDemo demo = new SyncQModeDemo();
  4.         demo.startThreadA();
  5.         //控制线程执行时间
  6.         Thread.sleep(100);
  7.         demo.startThreadB();
  8.         Thread.sleep(100);
  9.         demo.startThreadC();
  10.     }
  11.     final Object lock = new Object();
  12.     public void startThreadA() {
  13.         new Thread(() -> {
  14.             synchronized (lock) {
  15.                 log.debug("A get lock");
  16.                 try {
  17.                     Thread.sleep(300);  //对应情况1,模拟业务时间
  18.                     //lock.wait(300);  //对应情况2
  19.                 } catch (InterruptedException e) {
  20.                     e.printStackTrace();
  21.                 }
  22.                 log.debug("A release lock");
  23.             }
  24.         }, "thread-A").start();
  25.     }
  26.     public void startThreadB() {
  27.         new Thread(() -> {
  28.             synchronized (lock) {
  29.                 try {
  30.                     log.debug("B get lock");
  31.                     Thread.sleep(500);
  32.                 } catch (InterruptedException e) {
  33.                     e.printStackTrace();
  34.                 }
  35.                 log.debug("B release lock");
  36.             }
  37.         }, "thread-B").start();
  38.     }
  39.     public void startThreadC() {
  40.         new Thread(() -> {
  41.             synchronized (lock) {
  42.                 log.debug("C get lock");
  43.             }
  44.         }, "thread-C").start();
  45.     }
  46. }
复制代码
 
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表