Java 使用;通知()&&引用;等待();而不是",;暂停();及;resume();控制线程

Java 使用;通知()&&引用;等待();而不是",;暂停();及;resume();控制线程,java,multithreading,applet,synchronization,synchronized,Java,Multithreading,Applet,Synchronization,Synchronized,我试图学习如何在java中暂停和恢复线程。我使用的是一个Applet,它实现了Runnable功能,有两个按钮“开始”和“停止” 线程的运行方法: public void run(){ while(true){ repaint(); try{ Thread.sleep(20); } catch(InterruptedException e) { e.printStackTrace(); } } } 现在,每当我尝试暂停或恢复线程时

我试图学习如何在java中暂停和恢复线程。我使用的是一个
Applet
,它实现了Runnable功能,有两个按钮“开始”和“停止”

线程的运行方法:

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和notify。尝试使用

synchronized (th) {
    th.notify();
}

wait()
一样,您不能只调用
notify
wait
。你得等点什么。在调用
notify
之前,您必须确保不再需要等待

如果块尚未同步,则说明设计中存在错误

除非你有什么事要等,否则你怎么能叫
wait
?如果你没有检查,你怎么知道还有什么要等呢?在不与控制该事件是否发生的代码同步的情况下,如何进行检查

除非发生了需要通知线程的事情,否则如何调用
notify
?如果你没有持有可以告诉另一个线程的锁,那么怎么会发生另一个线程关心的事情呢

您应该像这样使用
wait

while (something_to_wait_for()) wait();
等待的东西应该检查受同步保护的东西。你不能让等待的东西同步,因为你有一个竞争条件——如果在等待的东西返回后,但在你进入等待之前发生了什么?那么你在等待已经发生的事情!因此,从根本上说,您需要同步。如果您只是在末尾添加它,那么您的设计就被破坏了


您的解决方案可能是添加一些等待的内容。也许您只需要一个简单的布尔变量。然后你的代码可以是
while(should_wait)wait()
应该等待=真
应该等待=false();notifyAll()
。您需要同步
以保护布尔值和
等待
/
通知
逻辑。

您误解了
等待()的工作原理。在
线程
对象上调用
等待
不会暂停该线程;相反,它告诉当前运行的线程等待其他事情发生。为了解释原因,我需要备份一点并解释
synchronized
的实际功能

当您输入
synchronized
块时,您将获得与对象关联的监视器。比如说,

synchronized(foo) {
获取与对象
foo
关联的监视器

foo.wait();
拥有监视器后,在退出同步块之前,其他线程无法获取监视器。这是
等待
通知
进入的地方

wait
是对象类上的一个方法,它告诉当前运行的线程临时释放它所持有的监视器。这允许其他线程在
foo
上同步

foo.wait();
除非有人在
foo
上调用
notify
notifyAll
(或线程被中断),否则此线程不会继续。一旦发生这种情况,此线程将尝试重新获取
foo
的监视器,然后继续。请注意,如果任何其他线程正在等待获取监视器,那么它们可能会首先进入——无法保证JVM将分发锁的顺序。请注意,如果没有人调用
notify
notifyAll
,则
wait()
将永远等待。通常最好使用另一种需要超时的
wait
。当有人调用
notify
/
notifyAll
或超时过期时,该版本将被唤醒

因此,您需要一个线程来进行等待,另一个线程来进行通知。
wait
notify
都必须将监视器保持在其试图等待或通知的对象上;这就是您看到非法监视器状态异常的原因

举个例子可以帮助您理解:

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();
        }
    }
}
然后,小程序代码可以执行以下操作:

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在锁上获取监视器
  • 线程B更新“暂停”变量并调用LOCK.notifyAll
  • 没有线程在等待锁定,因此没有发生任何有趣的事情
  • 线程B释放监视器on锁
  • 线程A醒来,再次通过其循环
  • 线程A在锁上获取监视器
  • 线程A看到它应该暂停,所以它调用LOCK.wait
  • 此时线程A挂起,等待有人调用notifyAll。线程A释放监视器on锁
  • 一段时间后,用户单击“恢复”
  • 线程B调用scheduler.resume
  • 苏氨酸