管程(Moniter): 并发编程的基本心法

简介: 在吃透 Syncchronized 原理 中介绍了关于 Synchronize的实现原理,无论是同步方法还是同步代码块,无论是ACC_SYNCHRONIZED还是monitorenter、monitorexit都是基于Monitor实现的,那么这篇来介绍下什么是Monitor。

所谓管程:指的是管理共享变量以及对共享变量的操作过程,让它们支持并发。


翻译为 Java 就是管理类的成员变量和成员方法,让这个类是线程安全的。


是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。这些共享资源一般是硬件设备或一群变量。管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。与那些通过修改数据结构实现互斥访问的并发程序设计相比,管程实现很大程度上简化了程序设计。 管程提供了一种机制,线程可以临时放弃互斥访问,等待某些条件得到满足后,重新获得执行权恢复它的互斥访问。


MESA 模型


在管程的发展史上,先后出现过三种不同的管程模型,分别是:Hasen 模型、Hoare 模型和 MESA 模型。其中,现在广泛应用的是 MESA 模型,并且 Java 管程的实现参考的也是 MESA 模型。所以今天我们重点介绍一下 MESA 模型。


在并发领域,有两个核心问题:一个是互斥,一个是同步。管程就是来解决这两个问题的。


  • 互斥:同一时刻只允许一个线程访问共享资源。


  • 同步:线程之间如何通信、协作。


管程互斥与同步实现


它的思路很简单,将共享变量以及对共享变量的操作统一封装起来。如下图所示,管程 A 将共享变量 data 和相关的操作入队enq()、出队deq() 封装起来。线程 A 和线程 B想访问共享变量 data ,只能通过调用管程提供的 enq()deq() 。当然前提是 enq()deq() 保证互斥性,只允许一个线程进入管程。是不是很有面向对象的感觉。


在管程模型里,共享变量和对共享变量的操作是被封装起来的,图中最外层的框就代表封装的意思。框的上面只有一个入口,并且在入口旁边还有一个入口等待队列。当多个线程同时试图进入管程内部时,只允许一个线程进入,其他线程则在入口等待队列中等待。这个过程类似就医流程的分诊,只允许一个患者就诊,其他患者都在门口等待。


管程里还引入了条件变量的概念,而且每个条件变量都对应有一个等待队列,如下图,条件变量 A 和条件变量 B 分别都有自己的等待队列。


通过条件通知去唤醒等待队列的线程竞争 锁资源。


我们通过一段代码说明,实现一个阻塞队列,队列分别有出队与入队,都是要先获取互斥锁,就像管程中的入口。


  1. 对于入队操作,如果队列已满,就需要等待直到队列不满,所以这里用了notFull.await();


  1. 对于出队操作,如果队列为空,就需要等待直到队列不空,所以就用了notEmpty.await();


  1. 如果入队成功,那么队列就不空了,就需要通知条件变量:队列不空notEmpty对应的等待队列。


  1. 如果出队成功,那就队列就不满了,就需要通知条件变量:队列不满notFull对应的等待队列。


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();
    }  
  }
}


在这段示例代码中,我们用了 Java 并发包里面的 Lock 和 Condition,如果你看着吃力,也没关系,后面我们还会详细介绍,这个例子只是先让你明白条件变量及其等待队列是怎么回事。需要注意的是:await() 和前面我们提到的 wait() 语义是一样的;signal() 和前面我们提到的 notify() 语义是一样的。管程通过条件队列通信实现了同步,为我们 Java中的并发编程提供了基本支持。

相关文章
|
1月前
|
安全 Java
管程(Moniter): 并发编程的基本心法
管程(Moniter): 并发编程的基本心法
40 0
|
4天前
|
Java
并发编程的艺术:Java线程与锁机制探索
【6月更文挑战第21天】**并发编程的艺术:Java线程与锁机制探索** 在多核时代,掌握并发编程至关重要。本文探讨Java中线程创建(`Thread`或`Runnable`)、线程同步(`synchronized`关键字与`Lock`接口)及线程池(`ExecutorService`)的使用。同时,警惕并发问题,如死锁和饥饿,遵循最佳实践以确保应用的高效和健壮。
11 2
|
18天前
|
安全 Java 开发者
Java并发编程的艺术:解锁多线程同步的奥秘
本文将深入探讨Java并发编程的核心概念,揭示多线程环境下同步机制的工作原理与实践技巧。我们将从基础的synchronized关键字讲起,逐步过渡到高级的Lock接口和并发工具类,最后通过实例分析来加深理解。文章不仅旨在为初学者提供一个清晰的并发编程入门指南,同时也希望能够帮助有一定经验的开发者巩固和提升他们的并发处理能力。
|
30天前
|
算法 安全 Java
探索Java并发编程的奥秘
【5月更文挑战第26天】 在多线程的世界里,Java并发编程是一块重要的领域。本文将深入探讨Java并发编程的基本概念、实现方式以及常见问题,帮助读者更好地理解和掌握并发编程的技巧。
24 2
|
1月前
|
消息中间件 安全 Ubuntu
【操作系统原理】—— 线程同步
【操作系统原理】—— 线程同步
24 1
|
1月前
|
存储 安全 算法
Java并发之舞:掌握线程同步的精髓
Java并发之舞:掌握线程同步的精髓
51 0
Java并发之舞:掌握线程同步的精髓
|
1月前
|
Java
深入理解Java中的并发编程:线程与锁的奥秘
【4月更文挑战第2天】本文将深入探讨Java并发编程的核心概念,包括线程和锁。我们将详细解析线程的生命周期,以及如何通过锁来保证数据的一致性和同步。此外,我们还将讨论一些常见的并发问题,如死锁、活锁和饥饿,以及如何解决这些问题。
|
1月前
|
Java
并发编程的艺术:Java线程与锁机制的实践
并发编程的艺术:Java线程与锁机制的实践
285 1
|
9月前
|
缓存 Java 编译器
【并发编程的艺术】Java内存模型总结
基于前面系列文章,我们可以对Java内存模型(JMM)进行总结
137 0
|
10月前
|
算法 安全 Java
深入理解多线程编程:并发世界的探险
在计算机编程领域,随着多核处理器的普及,多线程编程成为了一种常见的技术。多线程编程可以提高程序的性能,充分利用多核处理器的计算能力。然而,多线程编程并不容易,它引入了并发性和同步问题,需要开发者仔细思考和处理线程之间的竞争条件。本文将深入探讨多线程编程的概念、技术和最佳实践,帮助读者更好地应对并发编程挑战。
136 0