你误解了它的wait()
工作原理。调用对象不会暂停该线程wait
;Thread
相反,它告诉当前正在运行的线程等待其他事情发生。为了解释原因,我需要备份一下并解释synchronized
实际做了什么。
当您输入一个synchronized
块时,您将获得与对象关联的监视器。例如,
synchronized(foo) {
获取与对象关联的监视器foo
。
一旦你有了监视器,在你退出同步块之前,没有其他线程可以获取它。这就是进来的wait
地方。notify
wait
是 Object 类上的一个方法,它告诉当前正在运行的线程暂时释放它持有的监视器。这允许其他线程在foo
.
foo.wait();
notify
在其他人调用或notifyAll
打开foo
(或线程被中断)之前,该线程不会恢复。一旦发生这种情况,该线程将尝试重新获取监视器foo
,然后继续。请注意,如果任何其他线程正在等待获取监视器,那么它们可能会先进入 - 无法保证 JVM 分发锁的顺序。请注意,wait()
如果没有人打电话notify
或notifyAll
. 通常最好使用wait
需要超时的其他形式。notify
当有人呼叫/notifyAll
或超时到期时,该版本将唤醒。
所以,你需要一个线程来做等待,另一个线程来做通知。两者都wait
必须notify
将监视器保持在他们试图等待或通知的对象上;这就是您看到 IllegalMonitorStateException 的原因。
一个例子可以帮助你理解:
class RepaintScheduler implements Runnable {
private boolean paused = false;
private final Object LOCK = new Object();
public void run() {
while (true) {
synchronized(LOCK) {
if (paused) {
try {
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
repaint();
}
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void pause() {
synchronized(LOCK) {
paused = true;
LOCK.notifyAll();
}
}
public void resume() {
synchronized(LOCK) {
paused = false;
LOCK.notifyAll();
}
}
}
然后您的 Applet 代码可以执行以下操作:
public void init() {
RepaintScheduler scheduler = new RepaintScheduler();
// Add listeners that call scheduler.pause and scheduler.resume
btn_increment.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {
scheduler.resume();
}});
btn_decrement.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {
scheduler.pause();
}});
// Now start everything up
Thread t = new Thread(scheduler);
t.start();
}
请注意,Applet 类不关心调度程序如何暂停/恢复,并且没有任何同步块。
所以这里可能发生的一系列事件是:
- 线程 A 开始运行重绘调度程序。
- 线程 A 休眠 20 毫秒。
- 线程 B(事件调度线程)接收到按钮点击;称为“暂停”。
- 线程 B 获得 LOCK 上的监视器。
- 线程 B 更新“暂停”变量并调用 LOCK.notifyAll。
- 没有线程正在等待 LOCK,因此没有任何有趣的事情发生。
- 线程 B 在 LOCK 上释放监视器。
- 线程 A 被唤醒,再次通过它的循环。
- 线程 A 获得 LOCK 上的监视器。
- 线程 A 看到它应该暂停,所以它调用 LOCK.wait。
- 此时线程 A 挂起,等待有人调用 notifyAll。线程 A 在 LOCK 上释放监视器。
- 一段时间后,用户单击“恢复”。
- 线程 B 调用 scheduler.resume。
- 线程 B 获得 LOCK 上的监视器。
- 线程 B 更新“暂停”变量并调用 LOCK.notifyAll。
- 线程 A 看到“notifyAll”并唤醒。它试图获取 LOCK 上的监视器,但它由线程 B 持有,因此线程 A 阻塞。
- 线程 B 在 LOCK 上释放监视器。
- 线程A获得监视器并进行。
这一切有意义吗?
不需要单独的 LOCK 变量;我这样做是为了强调您没有在Thread
实例上调用等待/通知这一事实。同样,RepaintScheduler 内部的逻辑并不理想,只是用来说明如何使用等待/通知。