在Java程序中,当一个以上的线程是活的时,就会出现并发现象。如果一个线程已经启动但尚未终止,它就是活的。在本篇博客,我们将介绍一个简单的Java多线程程序的例子,除了每个Java程序中的主执行线程外,还有两个并发的活动线程。示例程序中的线程并不直接互动。关于线程如何交互的话题将留到后面讨论。
2. ThreadDemo Example – Model
该示例程序驱动如图中描述:
每个线程,A和B,都可以通过按下适当的按钮来运行和暂停。当一个线程运行时,与之相关的显示器会旋转。当线程暂停时,旋转停止。当一个线程暂停时,其背景颜色被设置为红色,当它运行时,背景颜色被设置为绿色。线程之间不相互影响,但是当按钮被按下时,它们与执行的Java主线程相互影响。
小程序中的两个线程的行为由以下ROTATOR过程来模拟。
ROTATOR = PAUSED,
PAUSED = (run->RUN | pause->PAUSED),
RUN = (pause->PAUSED |{run,rotate}->RUN).
流程在进入RUN状态之前不能执行旋转动作。这只能在运行动作之后发生,运行动作是指按下运行按钮。当暂停动作发生时——模拟暂停按钮——进程移回暂停状态,在该状态下,旋转动作不能发生。该模型意味着ROTATOR的实现永远运行——没有办法停止它。对永远运行的线程进行编程并不是好的做法;当例如浏览器调用Applet.stop()方法时,它们应该有秩序地终止。Java的设计者不建议使用Thread.stop()来终止一个线程的执行。相反,他们建议使用Thread.interrupt(),该方法会引发InterruptedException,允许线程在终止之前进行清理。我们可以在ROTATOR进程中加入终止,如下图所示。
ROTATOR = PAUSED,
PAUSED = (run->RUN |pause->PAUSED
|interrupt->STOP),
RUN = (pause->PAUSED |{run,rotate}->RUN
|interrupt->STOP).
我们将a.interrupt和b.interrupt动作重新标记为同一个动作stop,表明当浏览器调用Applet.stop()时,我们总是同时中断两个线程。构建好模型后,我们可以使用LTSA工具将其动画化,以检查其行为是否与我们期望的ThreadDemo小程序的行为相一致。下图是LTSA Animator窗口的屏幕截图。那些可以选择执行的动作被打上了勾。在图中,动作a.run使进程a处于可以发生a.rotate动作的状态,而进程b不能执行b.rotate动作,因为b.run还没有发生。
事实上,在实现中,环境是由Java程序的执行主线程提供的。当然,我们也可以将这个主线程建模为一个分享动作的进程。显示器可以在任何时候旋转,按钮可以在任何时候被按下。因此,这个主线程可以被建模为。
MAIN = ({a.rotate,a.run,a.pause,stop,
b.rotate,b.run,b.pause}->MAIN).
用THREAD_DEMO组成MAIN并不修改THREAD_DEMO的行为,因为它没有对动作提供任何额外的排序约束。