3

我正在尝试学习如何在 java 中暂停和恢复线程。我正在使用Applet具有implements Runnable2 个按钮“开始”和“停止”的按钮。

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()and ,前面的代码可以完美运行resume(),但是文档指出使用notify()andwait()而不是同步。我尝试将单词添加synchronizedactionPerformed方法中,但它仍然抛出异常。

有人可以解释为什么这不起作用以及如何解决同步问题吗?很少有解释点真的会有很大帮助;)

4

3 回答 3

10

你误解了它的wait()工作原理。调用对象不会暂停该线程waitThread相反,它告诉当前正在运行的线程等待其他事情发生。为了解释原因,我需要备份一下并解释synchronized实际做了什么。

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

synchronized(foo) {

获取与对象关联的监视器foo

一旦你有了监视器,在你退出同步块之前,没有其他线程可以获取它。这就是进来的wait地方。notify

wait是 Object 类上的一个方法,它告诉当前正在运行的线程暂时释放它持有的监视器。这允许其他线程在foo.

foo.wait();

notify在其他人调用或notifyAll打开foo(或线程被中断)之前,该线程不会恢复。一旦发生这种情况,该线程将尝试重新获取监视器foo,然后继续。请注意,如果任何其他线程正在等待获取监视器,那么它们可能会先进入 - 无法保证 JVM 分发锁的顺序。请注意,wait()如果没有人打电话notifynotifyAll. 通常最好使用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 类不关心调度程序如何暂停/恢复,并且没有任何同步块。

所以这里可能发生的一系列事件是:

  • 线程 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 内部的逻辑并不理想,只是用来说明如何使用等待/通知。

于 2012-11-11T19:17:39.777 回答
3

你不能只打电话notifywait。你必须等待一些东西。在调用之前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逻辑。

于 2012-11-11T18:47:42.417 回答
1

我认为您必须在线程上同步才能调用等待和通知。尝试使用

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

和一样wait()

于 2012-11-11T18:46:54.323 回答