你误解了如何wait()
作品。呼唤wait
on a Thread
对象不会暂停该线程;相反,它告诉当前正在运行的线程等待其他事情发生。为了解释原因,我需要稍微解释一下synchronized
事实上确实如此。
当您输入一个synchronized
阻止您获得monitor与一个对象相关联。例如,
synchronized(foo) {
获取与该对象关联的监视器foo
.
一旦获得监视器,在您退出同步块之前,其他线程都无法获取它。这是哪里wait
and notify
进来。
wait
是 Object 类上的一个方法,它告诉当前正在运行的线程暂时释放它所持有的监视器。这允许其他线程同步foo
.
foo.wait();
在其他人调用之前,该线程不会恢复notify
or notifyAll
on foo
(或者线程被中断)。一旦发生这种情况,该线程将尝试重新获取监视器foo
然后继续。请注意,如果任何其他线程正在等待获取监视器,那么它们可能会先进入 - 无法保证 JVM 分发锁的顺序。注意wait()
如果没有人打电话就会永远等待notify
or notifyAll
。通常最好使用其他形式wait
这需要超时。当有人打电话时该版本会唤醒notify
/notifyAll
或者当超时已过时。
因此,您需要一个线程来执行等待,并需要另一个线程来执行通知。两个都wait
and 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 变量;我这样做是为了强调这样一个事实:您没有在某个事件上调用 wait/notifyThread
实例。同样,RepaintScheduler 内部的逻辑并不理想,但只是为了说明如何使用等待/通知。