5.2监视器对象
1.问题
许多应用程序包含其方法被多个客户机线程并发调用的对象。这些方法通常修改其对象的状态。因此,为了使这些并发应用程序能正确地执行,有必要对对象的访问进行同步和调度。对于这个问题,必须考虑四个强制条件:
1)为了事务分离并避免对象状态不受控制地修改,面向对象编程人员习惯于只通过对象的接口方法访问对象。扩展这种面向对象编程模型以避免对象的数据不受控制的并发修改相对比较直接。因此对象的接口方法应该定义它的同步边界,在同一对象中某一时刻仅有一个方法能处于活动状态。
2)如果客户机必须显式获取和释放低层同步机制,如信号灯、互斥或条件变量,那么并发应用程序将更难进行编程。因此,对象应该负责确保它们需要同步的任何方法被透明地串行化,而不需要客户机的显式介入。
3)如果一个对象的方法在执行时必须阻塞,它们应该能够自愿放弃它们的控制线程,这样其他客户机线程调用的方法就可以访问这个对象。这种属性有助于防止死锁并能够利用硬、软件平台提供的并发机制。
4)一个方法自愿放弃它的控制线程后,它必须让对象处于稳定状态,即必须保持与对象有关的不定式。类似地,只有在对象处于稳定状态时才可以在对象内恢复一个方法的执行。
2.解决方案
将对象方法的访问同步化,以便一次仅有一个方法可以执行。
细节:对于每个被多个客户机线程并发访问的对象,把它定义为一个监视器对象。客户机只能通过它的同步化方法访问监视器对象定义的函数。为了防止对象内部状态的竞争条件,一次只能有一个同步化方法能在监视器对象内运行。为了将对象状态的并发访问串行化,每个监视器对象都包含一个监视器锁。同步化方法可以基于一个或多个监视器对象关联的监视器条件确定它们挂起和恢复执行的环境。
3.结构
监视器对象提供一个或多个方法。为了保护监视器对象的内部状态不被非受控地修改和防止竞争条件,所有的客户机必须仅通过这些方法访问监视器对象。每个方法在调用它的客户机线程中执行,因为监视器对象没有自己的控制线程。
同步化方法实现由监视器对象提供的线程安全的功能。为了防止竞争条件,一次只能有一个同步化方法可以在监视器对象中执行。不管并发调用对象的同步化方法的线程数量或者对象类中同步化方法的数量是多少,都可采用这种规则。
每个监视器对象都包含有自己的监视器锁。同步化方法基于完全对象原则使用该锁将方法调用串行化,每个同步化的方法在进入或退出对象时,必须请求和释放对象的监视器锁。这个协议确保了不论何时同步化方法执行操作访问或修改对象的状态,监视器锁都能被保持。
监视器条件。运行在不同线程中的多个同步化方法可以通过与监视器对象关联的监视器条件彼此等待和通知,从而协作调度它的执行顺序。同步化方法将它们的监视器锁与监视器条件结合使用,确定它们挂起或恢复处理时所处的环境。
4.实现
1)定义监视器对象的接口方法。监视器对象的接口向客户机提供一系列方法。接口方法通常是同步的。就是说,在一个监视器对象中一次只有一个方法可以被线程执行。
2)定义监视器对象的实现方法。监视器对象通常包含内部实现方法,用这种同步化的接口方法来实现对象的功能。这种设计有助于将核心的监视器对象功能特性与它的同步和调度逻辑分离开。
3)定义监视器对象的内部状态和同步机制。监视器对象包含定义其内部状态的数据成员。这种状态必须受到保护,免受由非同步并发访问引起的竞争条件的破坏。因此,监视器对象包含一个监视器锁使它的同步化方法被串行执行,以及一个或多个监视器条件,用于调度监视器对象中同步化方法的执行。一般以下每种情况都对应一个独立的监视器条件:
·第一种情况,同步化方法必须将它们的处理挂起,等待某些状态改变事件发生。
·第二种情况,同步化方法必须恢复其他线程,这些线程的同步化方法将其自身挂起,等待监视器条件。
监视器锁可以使用互斥来实现。当持有互斥的线程执行临界区的代码时,互斥使协作的线程等待,直到特定事件发生或者复杂的条件表达式获得一个特定的稳定状态。条件表达式通常访问对象或线程间共享的状态变量,它们可以用来实现保护挂起模式。
4)实现监视器对象的所有方法和数据成员。
4.1)初始化数据成员。将对象特定的数据成员,以及监视器锁和任何监视器条件初始化。
4.2)应用线程安全接口模式。按照线程安全接口模式实现接口方法和实现方法。
5.结论
优点:
1)简化了并发控制。监视器对象模式为在协作线程间共享对象提供了简明的编程模型。
2)简化了方法执行的调度。同步化方法使用它们的监视器条件确定它们可能挂起或恢复它们的执行以及协作监视器对象的环境。
不足:
1)当串行化多个线程对监视器对象的访问时,由于争用增加,单监视器锁的使用会限制可扩展性。
2)监视器对象的功能特性和它的同步机制的紧密耦合了导致复杂的可扩展性语义。通过独立的调度程序的参与,将主动对象的功能特性与它的同步策略分离是相对直接口。但是,监视器对象的同步化和调度逻辑通常和其方法的功能特性紧密耦合。这种耦合常常使监视器对象比主动对象更高效。不过,同时也很难在不修改监视器对象的方法实现的情况下改变它们的同步策略和机制。
3)由于继承异常的问题,也很难透明地继承监视器对象。当子类需要不同的同步机制时,该问题抑制了对同步化方法实现的重用。降低监视器对象中同步和功能特性之间的耦合度的方法之一是使用面向特征的编程,使用策略化加锁和线程安全接口模式。
4)嵌套的监视器锁定。可以通过在多监视器条件间共享一个监视器锁而避免嵌套的监视器阻塞。