我正在尝试学习如何在java中暂停和恢复一个线程。我正在使用Applet
,implements Runnable
有两个按钮“开始”和“停止”。
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();
}
}
}
现在,每当我尝试暂停或恢复该线程时,都会抛出异常:
Exception in thread "AWT-EventQueue-1" java.lang.IllegalMonitorStateException
注意:
如果我使用弃用的方法suspend()
和resume()
,前面的代码会运行得很好,但文档指出使用notify()
和wait()
而不是同步。我尝试将synchronized
添加到actionPerformed
方法,但它仍然会抛出异常。
有人可以解释为什么这不起作用以及如何解决同步问题?很少有解释点可以提供很多帮助;)
答案 0 :(得分:10)
您误解了wait()
的工作原理。在wait
对象上调用Thread
不会暂停该线程;它反而告诉当前正在运行的线程等待其他事情发生。为了解释原因,我需要稍微补充并解释synchronized
实际上做了什么。
当您输入synchronized
块时,您将获得与对象关联的监视器。例如,
synchronized(foo) {
获取与对象foo
关联的监视器。
一旦有了监视器,在退出synchronized块之前,没有其他线程可以获取它。这是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类不关心调度程序如何暂停/恢复,也没有任何同步块。
所以这里可能的一系列事件是:
这一切都有意义吗?
不需要单独的LOCK变量;我已经这样做了,强调你没有在Thread
实例上调用wait / notify。类似地,RepaintScheduler中的逻辑并不理想,但仅用于说明如何使用wait / notify。
答案 1 :(得分:3)
您不能只是致电notify
和wait
。您必须等待获取某些内容。在致电notify
之前,你必须做到这一点,以便没有什么可以等待了。
如果您的块尚未同步,那么您的设计就会出现问题。
除非你有什么值得期待的,否则如何致电wait
?如果你没有检查过,你怎么知道还有什么值得期待的?如何在不与控制该事件是否发生的代码同步的情况下进行检查?
你怎么能打电话给notify
,除非发生了一些你需要通知线程的事情?如果你没有持有可以告诉该线程的锁定,另一个线程会怎样关心呢?
你应该像这样使用wait
:
while (something_to_wait_for()) wait();
something_to_wait_for
应检查受同步保护的内容。并且你不能使something_to_wait_for
同步,因为那时你有一个竞争条件 - 如果在something_to_wait_for
返回之后但在你输入wait
之前发生了什么?然后你在等待已经发生的事情!因此,您需要从根本上进行同步。如果您只是在最后添加它,那么您的设计就会被破坏。
您的案例中的解决方案可能是要添加等待的东西。也许你需要一个简单的布尔变量。然后,您的代码可以是while (should_wait) wait();
,should_wait = true;
和should_wait = false(); notifyAll()
。您需要synchronized
来保护布尔值和wait
/ notify
逻辑。
答案 2 :(得分:1)
我认为你必须在线程上同步才能调用wait和notify。尝试使用
synchronized (th) {
th.notify();
}
与wait()
相同。