前言
在并发编程领域,最核心的两个理念就是同步和互斥,并发编程就是围绕这两个核心概念来完成的。
- 互斥:同一时刻只能有一个线程持有共享资源
- 同步:多个线程之间协调、互作
在最初,人们利用信号量机制来实现互斥和同步,但是对于编程人员来说,在编码中嵌入大量的信号量操作,代码冗余,而且出错概率增大,后来就有了面向开发人员更友好的管程。
管程
回顾信号量机制,三个核心:
- 共享变量S,来表示共享资源的数量
- PV操作,这是操作系统的两个原语操作。对共享变量的修改只能通过PV操作来进行。
- P操作,请求资源,S = S -1
- V 操作,释放资源, S = S + 1
管程就是将对信号量的操作进行了封装,体现了面向对象的思想。
Monitor,管程,也称作监视器。
管程指的是管理共享变量以及对共享变量操作 的过程,来达到线程安全的目的
简单点说,就是将共享变量和对共享变量的操作 进行了封装,访问共享变量必须通过管程提供的方法来实现,管程保证了同一时刻只允许一个线程操作管程
那么对于上面提到的两个核心互斥和同步,管程是如何实现的呢?
- 互斥,管程的设计理念就是,同一时刻只能有一个线程操作管程(进入管程)
- 同步:一个线程进入到管程后,其他线程进入等待队列,等待唤醒;同时管程内部可以存在条件变量,对进入管程的线程进行判断,不满足条件会进入该条件变量的等待队列,最终也会进入统一的等待队列。
原理图:
以就诊的场景为例,一个医生只能接诊一位病人,未被接诊的病人需要在候诊室等待叫号,此时医生是共享变量(共享资源),一个病人就是一个线程,候诊室就是等待队列,拍CT就是条件变量。
- 一位病人接诊,此时医生发现病人,病人满足所有的条件,就诊完成,病人离开,去候诊室中的叫下一个号的病人。
- 如果一位病人接诊后,此时医生发现病人不满足条件,还需要去拍CT,病人离开,下一个病人进来,当这个病人做完CT后,重新在候诊室排队等待被叫号。
总结:
管程对共享变量以及对其的操作进行了封装
管程的组成部分:
- 共享变量
- 对共享变量的操作
- 等待队列
一个管程可以管理多个共享变量。
同一时刻只允许一个线程进入(访问)管程,并修改共享状态变量S = 1, 此时其他线程检测到共享变量被修改,那么就进入到等待队列
进入到管程内的线程,就可以来操作共享的资源了,管程内可以设置条件变量,同时此条件变量也可以拥有一个自己的等待队列,如果线程不满足条件,此线程退出管程,将此线程加入到此条件等待队列中,当在此队列中出队后,然后重新去竞争管程,失败就加入到等待队列中
在C语言中,管程是一个程序结构代码,在面向对象的语言中,管程是一个对象。
JUC中的AQS就是基于管程来实现的,而JUC中的大部分的锁都是继承AQS来实现的。Java中的synchronized中的监视器锁对象也是基于管程实现的。
参考资料
锁原理 - 信号量 vs 管程:JDK 为什么选择管程 - binarylei - 博客园