我正在尝试学习如何在 Java 中暂停和恢复线程。我正在使用一个Applet
,它实现Runnable
有两个按钮“Start”和“Stop”。
public void init(){
th = new Thread(this);
th.start();
btn_increment = new Button("Start");
btn_increment.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent ev){
th.notify();
}
});
add(btn_increment);
btn_decrement = new Button("Stop");
btn_decrement.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent ev){
try{
th.wait();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
});
add(btn_decrement);
}
线程的run方法:
public void run(){
while(true){
repaint();
try{
Thread.sleep(20);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
现在每当我尝试暂停或恢复线程时都会抛出异常:
线程“AWT-EventQueue-1”java.lang.IllegalMonitorStateException 中的异常
注意事项:
如果我使用已弃用的方法 suspend()
和 resume()
,之前的代码运行完美,但文档指出使用 notify()
和 wait()
而不是同步。我尝试将单词 synchronized
添加到 actionPerformed
方法中,但它仍然抛出异常。
有人可以解释为什么这不起作用以及如何解决同步问题吗?很少有解释点真的会有很大帮助;)
最佳答案
您误解了 wait()
的工作原理。在 Thread
对象上调用 wait
不会暂停该线程;它反而告诉当前运行的线程等待其他事情发生。为了解释原因,我需要回顾一下并解释 synchronized
的实际作用。
当您输入一个synchronized
block 时,您将获得与一个对象关联的监视器。例如,
synchronized(foo) {
获取与对象 foo
关联的监视器。
一旦你拥有了监视器,在你退出同步块(synchronized block)之前,其他线程都无法获得它。这就是 wait
和 notify
发挥作用的地方。
wait
是 Object 类的一个方法,它告诉当前运行的线程暂时释放它持有的监视器。这允许其他线程在 foo
上同步。
foo.wait();
此线程不会恢复,直到其他人调用 foo
上的 notify
或 notifyAll
(或线程被中断)。一旦发生这种情况,该线程将尝试重新获取 foo
的监视器,然后继续。请注意,如果任何其他线程正在等待获取监视器,那么它们可能会先进入 - 无法保证 JVM 分发锁的顺序。请注意,如果没有人调用 notify
或 notifyAll
,wait()
将永远等待。通常最好使用另一种需要超时的 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 类不关心调度程序如何暂停/恢复并且没有任何同步块(synchronized block)。
所以这里可能的事件顺序是:
- 线程 A 开始运行重绘调度程序。
- 线程 A hibernate 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
实例上调用 wait/notify 这一事实。同样,RepaintScheduler 内部的逻辑并不理想,但只是为了说明如何使用 wait/notify。
关于java - 使用 "notify()"& "wait()"而不是 "suspend()"和 "resume()"来控制线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13334207/