使用“notify()"&“等待()"而不是“suspend()"和“resume()"控制一个线程
我正在尝试学习如何在 java 中暂停和恢复线程.我正在使用一个 Applet
,它 implements Runnable
有 2 个按钮开始"和停止".
I'm trying to learn how to pause and resume a thread in java. I'm using an Applet
that implements Runnable
has 2 buttons "Start" and "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();
}
}
}
现在每当我尝试暂停或恢复线程时,都会引发异常:
Now whenever I try to to pause or resume the thread an exception is thrown:
线程AWT-EventQueue-1"java.lang.IllegalMonitorStateException 中的异常
注意事项:
如果我使用不推荐使用的方法 suspend()
和 resume()
,之前的代码可以完美运行,但文档指出使用 notify()
和 wait()
而不是同步.我尝试将单词 synchronized
添加到 actionPerformed
方法,但它仍然抛出异常.
The previous code runs perfectly if I use the deprecated methods suspend()
and resume()
, but the documentation points out at using notify()
and wait()
instead with synchronization. I tried adding the word synchronized
to the actionPerformed
method, but it still throws the exception.
谁能解释一下为什么这不起作用以及如何解决同步问题?很少有解释点真的会有很大帮助;)
Can someone please explain WHY this isn't working and how to solve the synchronization problem? Few explanation points would really be of much help ;)
推荐答案
你误解了 wait()
的工作原理.在 Thread
对象上调用 wait
不会暂停该线程;相反,它告诉当前正在运行的线程等待其他事情发生.为了解释原因,我需要备份一下并解释 synchronized
的实际作用.
You have misunderstood how wait()
works. Calling wait
on a Thread
object does not pause that thread; it instead tells the currently running thread to wait for something else to happen. To explain why, I'll need to back up a bit and explain what synchronized
actually does.
当您输入 synchronized
块时,您将获得与对象关联的 monitor.例如,
When you enter a synchronized
block you obtain the monitor associated with an object. For example,
synchronized(foo) {
获取与对象foo
关联的监视器.
obtains the monitor associated with the object foo
.
一旦你有了监视器,在你退出同步块之前,没有其他线程可以获取它.这就是 wait
和 notify
的用武之地.
Once you have the monitor, no other threads can obtain it until you exit the synchronized block. This is where wait
and notify
come in.
wait
是 Object 类上的一个方法,它告诉当前运行的线程暂时释放它持有的监视器.这允许其他线程在 foo
上进行同步.
wait
is a method on the Object class that tells the currently running thread to temporarily release the monitor it holds. This allows other threads to synchronize on foo
.
foo.wait();
在其他人在 foo
上调用 notify
或 notifyAll
(或线程被中断)之前,该线程不会恢复.一旦发生这种情况,该线程将尝试重新获取 foo
的监视器,然后继续.请注意,如果任何其他线程正在等待获取监视器,那么它们可能会先进入 - 无法保证 JVM 分发锁的顺序.请注意,如果没有人调用 notify
或 notifyAll
,wait()
将永远等待.通常最好使用需要超时的其他形式的 wait
.当有人调用 notify
/notifyAll
或超时时间已过时,该版本将被唤醒.
This thread will not resume until someone else calls notify
or notifyAll
on foo
(or the thread is interrupted). Once that happens, this thread will attempt to re-acquire the monitor for foo
and then continue. Note that if any other threads are waiting to obtain the monitor then they might get in first - there is no guarantee of the order the JVM will hand out locks. Note that wait()
will wait forever if no-one calls notify
or notifyAll
. It's usually best to use the other form of wait
that takes a timeout. That version will wake up when someone calls notify
/notifyAll
or when the timeout has expired.
因此,您需要一个线程来执行等待,而另一个线程来执行通知.wait
和 notify
都必须将监视器保持在它们试图等待或通知的对象上;这就是您看到 IllegalMonitorStateException 的原因.
So, you need one thread to do the waiting and a different thread to do the notifying. Both wait
and notify
must hold the monitor on the object they are trying to wait on or notify; this is why you are seeing the IllegalMonitorStateException.
一个例子可以帮助你理解:
An example might help you understand:
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 代码可以执行此操作:
Your Applet code can then do this:
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 类不关心调度程序如何暂停/恢复,并且没有任何同步块.
Note that the Applet class does not care about how the scheduler pauses/resumes and does not have any synchronized blocks.
所以这里可能发生的一系列事件是:
So a possible sequence of events here is:
- 线程 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 内部的逻辑并不理想,只是用来说明如何使用等待/通知.
Having a separate LOCK variable is not required; I've done that to highlight the fact that you are not calling wait/notify on a Thread
instance. Similarly, the logic inside the RepaintScheduler is not ideal but is just there to illustrate how wait/notify could be used.
相关文章