多线程理论基础(二)

本文探讨了多线程编程中的关键问题——互斥和同步,通过MESA模型详解管程机制,包括Java中的synchronized与Lock区别。还介绍了Hasen模型和Hoare模型在并发控制上的差异。重点讲解了Java中的synchronized在管程模型中的应用实例和概念剖析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多线程理论基础(二)


Java中的管程模型

一. 前言

并发编程最重要解决方案就是管程,管程并不是一种实际的方案,而是一种方法论,重点在于用什么样方案解决并发的问题,管程实际上就是指的是管理共享变量以及对共享变量的操作过程,让他们支持并发;

  • MESA模型
  • Hoare模型
  • Hasen模型

1.1 并发根本问题

并发领域最主要的两大问题:一是互斥,二是同步,互斥主要保证线程之间操作的原子性,同一个时刻只允许一个线程访问共享资源,同步则是线程之间通信,协作;

首先要明确一下管程的概念:管程又称为监视器,它是描述并实现对共享变量的管理与操作,使其在多线程下能正确执行的一个管理策略。可以理解成临界区资源的管理策略。

1.2 MESA模型示意图

MESA模型是管程的一种实现策略,Java使用的就是该策略
在这里插入图片描述

  • enterQueue管程的入口队列,当线程在申请进入管程中发现管程已被占用,那么就会进入该队列并阻塞。
  • varQueue条件变量等待队列,在线程执行过程中(已进入管程),条件变量不符合要求,线程被阻塞时会进入该队列。
  • condition variables:条件变量,存在于管程中,一般由程序赋予意义,程序通过判断条件变量执行阻塞或唤醒操作。
  • 阻塞和唤醒:wait()和await()就是阻塞操作。notify()和notifyAll()就是唤醒操作。

这样一想在JAVA中的synchronizedLock在管程模型中,前者只有一个条件变量,后者可以维护多个条件变量;

执行过程:

  1. 多个线程进入入口等待队列enterQueue,JVM会保证只有一个线程能进入管程内部,Synchronized中进入管程的线程随机。
  2. 进入管程后通过条件变量判断当前线程是否能执行操作,如果不能跳到step3,否则跳到step4。
  3. 条件变量调用阻塞方法,将当前线程放入varQueue,等待其他线程唤醒,跳回step1。
  4. 执行相应操作,执行完毕后调用notify/notifyAll等唤醒操作,唤醒对应varQueue中的一个或多个等待线程。
  5. 被唤醒的线程会从varQueue放入enterQueue中,再次执行step1。
  6. 被唤醒的线程不会立即执行,会被放入enterQueue,等待JVM下一次选择运行,而正在运行的线程会继续执行,直到程序执行完毕。

二.MESA模型

1.互斥问题

管程解决互斥问题的思路很简单,就是将共享变量及其对共享变量的操作统一封装起来。在下图中,管程 X 将共享变量 queue 这个队列和相关的操作入队 enq()、出队 deq() 都封装起来了;线程 A 和线程 B 如果想访问共享变量 queue,只能通过调用管程提供的 enq()、deq() 方法来实现;enq()、deq() 保证互斥性,只允许一个线程进入管程。

在这里插入图片描述

2.同步问题

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

在这里插入图片描述
Java中的管程模型

MESA 模型解决同步问题可以类比去医院就医。患者首先需要排队等待医生叫好,医生诊断被叫到号的患者。期间,患者如果需要进行其他辅助的检查,比如说排个 X 光,就需要去等待拍 X 光的医生叫好。患者拍完 X 光之后,再次回到上一个医生那里,等待医生再次诊断。

3.实际例子

下面的代码实现的是一个阻塞队列,阻塞队列有两个操作分别是入队和出队,这两个方法都是先获取互斥锁,类比管程模型中的入口。

  1. 对于入队操作,如果队列已满,就需要等待直到队列不满,所以这里用了notFull.await();
  2. 对于出队操作,如果队列为空,就需要等待直到队列不空,所以就用了notEmpty.await();
  3. 如果入队成功,那么队列就不空了,就需要通知条件变量:队列不空notEmpty对应的等待队列。
  4. 如果出队成功,那就队列就不满了,就需要通知条件变量:队列不满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();
        }
    }
}

三. Hasen 模型和Hoare 模型

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

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

四. 总结

Java 参考了 MESA 模型,语言内置的管程(synchronized)对 MESA 模型进行了精简.
在这里插入图片描述

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

五.关于我

Hello,我是球小爷,热爱生活,求学七年,工作三载,而今已快入而立之年,如果您觉得对您有帮助那就一切都有价值,赠人玫瑰,手有余香❤️.最后把我最真挚的祝福送给您及其家人,愿众生一生喜悦,一世安康!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值