管程, monitor
Contents
管程, monitor
管程, 监视器, Moniter
Java程序设计语言中,每个对象都可以作为一个管程。需要互斥使用的方法必须明确标示关键字 synchronized . 代码块也可以标示关键字synchronized.
不使用明确的条件变量, Java的这种管程在入口队列之外,使用单独的条件等待队列. 所有等待的线程进入这个队列,
所有的notify与notify all操作也施加于这个队列。这种方法已经被其它程序设计语言使用,如C#.
Java中的同步方法与其他经典管程有本质差别: Java没有内嵌的条件变量。反之,Java提供了两个过程wait和notify ,分别与sleep和wakeup等价,
不过,当它们在同步方法中使用时,它们不受竞争条件约束。理论上,方法wait可以被中断,它本身就是与中断有关的代码。Java需要显式表示异常处理。
在JAVA中是如何实现管程?
在Java虚拟机中,每个对象和类在逻辑上与管程相关联。为了实现管程的互斥能力,一个锁(有时也称为一个互斥锁)关联每个对象和类。这就是所谓操作系统书籍上的信号量(semaphore),互斥锁是一个二进制信号量。
如果一个线程拥有一些数据的锁,那么没有其他线程可以获取这个锁,直到拥有锁的线程释放它。当我们做多线程编程时,如果任何时候都需要编写一个信号量,这将是不方便。幸运的是,我们并不需要,因为JVM自动为我们实现了。
声明一个管程区域,这意味着数据不能被超过一个线程访问,java提供同步代码块和同步方法。一旦代码被嵌入synchronized关键字,它就是一个管程区域。该锁在后台通过JVM自动实现。
3.在JAVA的同步代码中,哪一部分是管程?
我们知道每个对象/类都关联一个管程。我认为更好的说法应该是每个对象都有一个管程,因为每个对象可以有它自己的临界区,并能够监控线程顺序。
为了使不同的线程协作,JAVA为提供了wait()和notify()来挂起线程和唤醒另外一个等待的线程。此外,还有其他3个方法:
wait(long timeout, int nanos)
wait(long timeout) notified by other threads or notified by timeout.
notify(all)
这些方法只能在同步代码块或同步方法中调用。因为所有的这些方法都需要线程持有对象的锁,所以只能通过同步来实现。
管程 (英语: Moniters,也称为监视器) 是一种程序结构,结构内的多个子程序 (对象或模块) 形成的多个工作线程互斥访问共享资源。
这些共享资源一般是硬件设备或一群变量。管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。
与那些通过修改数据结构实现互斥访问的并发程序设计相比,管程实现很大程度上简化了程序设计。
管程提供了一种机制,线程可以临时放弃互斥访问,等待某些条件得到满足后,重新获得执行权恢复它的互斥访问。
一个管程包含:
多个彼此可以交互并共用资源的线程
多个与资源使用有关的变量
一个互斥锁
一个用来避免竞态条件的不变量
一个管程的程序在运行一个线程前会先取得互斥锁,直到完成线程或是线程等待某个条件被满足才会放弃互斥锁。
若每个执行中的线程在放弃互斥锁之前都能保证不变量成立,则所有线程皆不会导致竞态条件成立。
当一个线程执行管程中的一个子程序时,称为占用(occupy)该管程. 管程的实现确保了在一个时间点,最多只有一个线程占用了该管程。这是管程的互斥锁访问性质。
当线程要调用一个定义在管程中的子程序时,必须等到已经没有其它线程在执行管程中的某个子程序。
在管程的简单实现中,编译器为每个管程对象自动加入一把私有的互斥锁。该互斥锁初始状态为解锁,
在管程的每个公共子程序的入口给该互斥锁加锁,在管程的每个公共子程序的出口给该互斥锁解锁。
条件变量(Condition Variable)
管程提供了一种实现互斥的简便途径,但这还不够。我们还需要一种办法使得线程在无法继续运行时被阻塞。
在生产者-消费者问题中,很容易将针对缓冲区满和缓冲区空的测试放到管程过程中,但是生产者在发现缓冲区满的时候如何阻塞呢?
解决的方法是引入条件变量 (condition variables) 以及相关的两个操作: wait和signal。
当一个管程过程发现它无法继续运行时 (例如,生产者发现缓冲区满) ,它会在某个条件变量上 (如full) 执行wait操作。
该操作导致调用进程自身阻塞,并且还将另一个以前等在管程之外的进程调入管程。
另一个线程,比如消费者,可以唤醒正在睡眠的伙伴进程,这可以通过对其伙伴正在等待的一个条件变量执行signal完成。
为了避免管程中同时有两个活跃进程,如果在一个条件变量上有若干进程正在等待,则在对该条件变量执行signal操作后,
系统调度程序只能在其中选择一个使其恢复运行。
线程可能需要等待某个条件P为真,才能继续执行。在一个忙等待(busy waiting)循环中
while not( P ) do skip
将会导致所有其它进程都无法进入临界区使得该条件P为真,该管程发生死锁.
解决办法是条件变量(condition variables). 概念上,一个条件变量就是一个线程队列(queue), 其中的线程正等待某个条件变为真。
每个条件变量c关联着一个断言P_c. 当一个线程等待一个条件变量,该线程不算作占用了该管程,因而其它线程可以进入该管程执行,
改变管程的状态,通知条件变量c其关联的断言P_c在当前状态下为真.
因此对条件变量存在两种主要操作:
wait c 被一个线程调用,以等待断言P_c被满足后该线程可恢复执行. 线程挂在该条件变量上等待时,不被认为是占用了管程.
signal c (有时写作notify c)被一个线程调用,以指出断言P_c现在为真.
当一个通知(signal)发给了一个有线程处于等待中的条件变量,则有至少两个线程将要占用该管程: 发出通知的线程与等待该通知的某个线程.
只能有一个线程占用该管程,因此必须做出选择。两种理论体系导致了两种不同的条件变量的实现:
阻塞式条件变量(Blocking condition variables),把优先级给了被通知的线程.
非阻塞式条件变量(Nonblocking condition variables),把优先级给了发出通知的线程.
隐式条件变量管程
Java程序设计语言中,每个对象都可以作为一个管程。需要互斥使用的方法必须明确标示关键字synchronized. 代码块也可以标示关键字synchronized.
不使用明确的条件变量, Java的这种管程在入口队列之外,使用单独的条件等待队列. 所有等待的线程进入这个队列,
所有的notify与notify all操作也施加于这个队列。这种方法已经被其它程序设计语言使用,如C#.
Java中的同步方法与其他经典管程有本质差别: Java没有内嵌的条件变量。反之,Java提供了两个过程wait和notify ,分别与sleep和wakeup等价,
不过,当它们在同步方法中使用时,它们不受竞争条件约束。理论上,方法wait可以被中断,它本身就是与中断有关的代码。Java需要显式表示异常处理。
Java的Concurrent包中ReentrantLock具有上述所有特性,可以用来实现管程。管程是一个非常实用且常见的技术,可以用来实现很多常用的并发数据结构,例如阻塞队列。
操作系统的互斥解决方案 临界区概念 临界区意味着这个区域是敏感的,因为一旦进程运行到这个区域,那么意味着会对公共数据区域或者文件进行操作,也意味着有可能有其它进程也正运行到了临界区。如果能够采用适当的方式,使得这两个进程不会同时处于临界区,那么就能避免竞态条件。
- 信号量 semaphore 信号量出现是为了解决什么问题?
一个进程的阻塞和唤醒是在不同进程中造成的。进程A调用了sleep()进入睡眠,而进程B调用了weekup (A)就会把进程A给唤醒,然而,就在它调用sleep()方法之前,由于时钟中断,进程B开始运行。结果就是。由于进程A未进入阻塞阶段,因此调用wakeup()信号就丢失了,等到进程A从之前中断的位置继续开始运行调用sleep()并进入了阻塞,可能再也没有进程去唤醒它了。 因此,进程的阻塞和唤醒需要额外引入一个变量来进行记录,用变量记录唤醒的次数,每次被唤醒,变量的值加1,这样即使wakeup操作先与sleep,但是wakeup操作会被记录到变量中,当进程进行sleep时候,因为已经有其他进程 唤醒过,此时可以认为这个进程不需要进入堵塞状态。
这个变量在操作系统概念里,被称为信号量,对信号量有两种操作,down和up down对应sleep,它会先检查信号量是否大于0,如果大于,则减1,进程此时无需堵塞,相当于消耗掉一次wakeup,若信号量为0,进程则会进入堵塞状态
up对应着 wakeup,进行up操作后,如果发现进程进程堵塞在这个信号量上,那么系统会选择其中一个进程将其唤醒,此时信号量的值不需要变化,但是被堵塞的进程已经少了一个,如果up操作时没有进程堵塞在信号量上,那么它会将信号量加1.
- 互斥量 mutex 互斥量是信号量的一种特例,它的值只有0和1,当我们不需要用到信号量的计数能力时候,我们可以使用互斥量,实际上就是同一时间只允许一个进程进入临界区,而信号量是允许多个进程同时进入。
Java互斥解决方案 管程 (英语:Monitors,也称为监视器) 是一种程序结构,结构内的多个子程序 (对象或模块)形成的多个工作线程互斥访问共享资源。这些共享资源一般是硬件设备或一群变量。管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。 管程提供了一种机制,线程可以临时放弃互斥访问,等待某些条件得到满足后,重新获得执行权恢复它的互斥访问。 更简单的说管程允许多线程互斥的访问共享变量。
- 管程 monitor 使用semaphore和mutex需要小心的使用down和up操作。为了更容易的编写出好的并发程序,在mutex和semaphore的基础上,提出了管程(monitor).
操作系统层面本身并不支持管程机制,实际上,管程是属于编程语言的范畴,当要使用管程时,先了解一下语言本身是否支持,例如 C 语言它就不支持管程,Java语言支持管程。
使用 monitor 机制的目的主要是为了互斥进入临界区,为了做到能够阻塞无法进入临界区的进程/线程,还需要一个monitor object来协助,这个 monitor object 内部会有相应的数据结构,例如列表,来保存被阻塞的线程;同时由于 monitor 机制本质上是基于 mutex 这种基本原语的,所以 monitor object 还必须维护一个基于 mutex 的锁。 此外,为了在适当的时候能够阻塞和唤醒 进程/线程,还需要引入一个条件变量,这个条件变量用来决定什么时候是“适当的时候”,这个条件可以来自程序代码的逻辑,也可以是在 monitor object 的内部,总而言之,程序员对条件变量的定义有很大的自主性。不过,由于 monitor object 内部采用了数据结构来保存被阻塞的队列,因此它也必须对外提供两个 API 来让线程进入阻塞状态以及之后被唤醒,分别是 wait 和 notify。
synchronized关键字在使用的时候,往往需要指定一个对象与之关联,一种是修饰方法,一种是修饰方法块.
synchronized修饰的是方法,字节码中并没有monitorentry和monitorexit,而是会出现ACC_SYNCHRONIZED标记,JVM层面根据这个标志来区分一个方法是否同步。当存在这个标记时候,执行线程将先持有Monitor对象,然后再执行方法,在该方法运行期间,其他线程将无法获取到Monitor对象。当方法执行完后,再释放Monitor对象。
总之,synchronzied需要关联monitor object。管程序机制中,monitor object 充当着维护 mutex以及定义 wait/signal API 来管理线程的阻塞和唤醒的角色。
jvm中的同步是基于进入和退出monitor object实现的,每个实例都会有个monitor object ,可以和对象一起创建,销毁。
monitor实现的原理 java中管程是由ObjectMonitor实现,而ObjectMonitor是由c++编写。
ObjectMonitor() { _header = NULL; _count = 0; //记录个数 _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; //持有monitor的线程 _WaitSet = NULL; //处于wait状态的线程,会被加入到_WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; //处于等待锁block状态的线程,会被加入到该列表 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; } 68039c5b7a2ea8c75aa7766c157d2d86
多个线程访问一段同步去代码,多个线程会被存放在EntryList集合中,并处堵塞状态的线程都会在该表中,当某个线程获取了到对象的monitor时候,管程是依靠底层操作系统的Mutex lock来实现互斥,当申请成功,就持有mutex锁。如果有线程调用wait()方法 就会释放持有的mutex锁,并且该线程会进入WaitSet集合中,等到下一次呗唤醒,如果线程顺利执行完方法,也会释放mutex锁
http://www.linuxidc.com/Linux/2014-09/106539.htm
http://www.lilihongblog.com/Blog/monitors+java+synchronization+mechanism
http://blog.5ibc.net/p/77567.html
Author -
LastMod 2012-11-15