目录
2.3 Condition Synchronization in Java
1. 引言
监视器是用于并发编程的语言特性。监视器封装了数据,这些数据只能被监视器的访问程序观察和修改。一次只能有一个访问程序被激活。因此,一个访问程序对封装在监视器中的数据变量有互斥的访问权。监视器听起来应该很熟悉,因为我们已经在之前的学习中看到了监视器的概念,尽管使用了不同的术语解释。一个对象满足了监视器的数据访问要求,因为它封装了数据,如果声明为私有,则只能由对象的方法访问。这些方法可以被同步以提供互斥的访问。因此,监视器在Java中被简单地表示为一个拥有同步方法的类。
除了确保对其封装的数据的访问是互斥的,监控器还支持条件同步。条件同步,正如这个术语所暗示的,允许监控器阻塞线程,直到一个特定的条件成立,比如一个计数变成非零,一个缓冲区变成空或者新的输入变得可用。本篇博文介绍了监视器中的条件同步是如何建模的,以及如何在Java中实现的。
2. Condition Synchronization
我们用一个简单的例子来说明条件同步的问题。一个停车场需要一个控制器,它只允许汽车在停车场未满时进入,并且为了保持一致性,不允许汽车在停车场内没有汽车时离开。下图是我们对停车场进行Java模拟的一个快照。
它描述了停车场已满的情况,障碍物被放下,不再允许其他车辆进入。汽车的到达和离开是由不同的线程模拟的。在图中,出发的线程已经停止,以允许停车场满员。因此,到达线程被阻止,无法继续前进。
2.1 Car Park Model
对一个系统进行建模的第一步是决定哪些事件或动作是值得关注的。在停车场系统中,我们可以抽象出一些细节,如显示面板的旋转和显示线程的启动和停止。因此,我们省略了建模中与运行、旋转、暂停和终止线程有关的动作。相反,我们只关注两个动作:汽车到达停车场和汽车离开停车场。这些动作分别被命名为arrive和depart。
下一步是确定进程。这些进程是到达进程、离开进程和控制进入停车场的进程。
到达进程和离开进程都是微不足道的。它们分别试图产生一连串的到达行动和一连串的离开行动。
停车场控制必须只允许在停车场有空间时发生到达行动,在停车场有车时发生离开行动。这表达了其他进程在与停车场交互时必须满足的同步条件。
下图给出了停车场模型。CARPARKCONTROL进程使用索引状态SPACES来记录停车场的可用车位数量。上面描述的控制要求已经用FSP的保护动作结构进行了建模。因此,在状态SPACES[0]中,不接受到达动作,在状态SPACES[N]中,不接受离开动作。
在下图中,停车场系统的行为被描述为一个LTS。这个LTS是由上图的模型直接生成的。它清楚地表明,在必须发生离开动作之前,最多可以接受四个到达动作。
2.2 Car Park Program
我们的并发系统模型将系统中的所有实体表示为进程。
在把一个模型的行为实现为Java程序时,我们必须决定哪些实体是主动的,哪些是被动的。
所谓主动,我们指的是发起行动的实体;这被实现为一个线程。
所谓被动,我们指的是对行动作出反应的实体;这被实现为一个监视器。
正如我们将在随后的例子中看到的,在模型中哪些进程在实现中成为线程,哪些成为监视器,这个决定并不总是很明确。然而,在停车场的例子中,这个决定是明确的。发起到达和离开动作的进程ARRIVALS和DEPARTURES应该作为线程来实现。响应到达和离开动作的CARPARKCONTROL进程应该是一个监视器。下图描述了停车场程序的类结构。
为了简化类图,我们省略了由ThreadPanel管理的DisplayThread和GraphicCanvas线程。与程序的并发执行有关的类是两个Runnable类:Arrivals和Departures,以及控制到达和离开的CarPark Control 类。这些类的实例是由CarPark小程序start()方法创建的。
public void start() {
CarParkControl c =
new DisplayCarPark(carDisplay,Places);
arrivals.start(new Arrivals(c));
departures.start(new Departures(c));
}
Arrivals和Departures是ThreadPanel类的实例,carDisplay是CarParkCanvas的实例,如类图所示。
到达和离开类的代码列在下面的程序中。这些类使用ThreadPanel.rotate()方法,该方法将旋转显示段移动的度数作为其参数。如果停车场已满,CarParkControl类必须阻止到达线程对arrive()的激活,如果停车场已空,则阻止离开线程对depart()的激活。我们如何在Java中实现这一点?
class Arrivals implements Runnable {
CarParkControl carpark;
Arrivals(CarParkControl c) {carpark = c;}
public void run() {
try {
while(true) {
ThreadPanel.rotate(330);
carpark.arrive();
ThreadPanel.rotate(30);
}
} catch (InterruptedException e){}
}
}
class Departures implements Runnable {
CarParkControl carpark;
Departures(CarParkControl c) {carpark = c;}
public void run() {
try {
while(true) {
ThreadPanel.rotate(180);
carpark.depart();
ThreadPanel.rotate(180);
}
} catch (InterruptedException e){}
}
}
2.3 Condition Synchronization in Java
Java为每个监视器提供了一个线程等待集;实际上是每个对象,因为任何对象都可能有一个与之相关的监视器同步锁。以下方法是由Object类提供的,所有其他的类都是从Object类派生出来的。
public final void notify()
唤醒一个正在等待这个对象的等待集的单线程。
public final void notifyAll()
唤醒所有在此对象的等待集上等待的线程。
public final void wait() throws InterruptedException
等待另一个线程的通知。等待的线程会释放与监视器相关的同步锁。当被通知时,该线程必须等待重新获得监视器,然后再继续执行。
如果被当前不 "拥有 "监视器的线程(即之前没有通过执行同步方法或语句获得同步锁的线程)调用,则操作失败。我们指的是,当一个线程获得与监视器相关的互斥锁时,它就进入了监视器,当它释放锁时,就退出了监视器。从上面的定义可以看出,调用wait()的线程会退出监视器。这允许其他线程进入监视器,当满足适当的条件时,可以调用notify()或notifyAll()来唤醒等待的线程。下图中描述了wait()和notify()的操作。