【问题标题】:Using "notify()" & "wait()" instead of "suspend()" and "resume()" to control a thread使用“notify()”和“wait()”代替“suspend()”和“resume()”来控制线程
【发布时间】:2012-10-31 07:34:24
【问题描述】:

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

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() 来代替同步。我尝试在actionPerformed 方法中添加单词synchronized,但它仍然抛出异常。

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

【问题讨论】:

  • 我不确定您是否能够在示例中看到 wait() 和 notify() 的效果。为什么不使用更简单的示例而不是使用小程序?至于您的问题,它不起作用,因为要调用wait() 或notify(),您必须拥有要调用该方法的对象的锁。

标签: java multithreading applet synchronization synchronized


【解决方案1】:

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

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

synchronized(foo) {

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

一旦你有了监视器,在你退出同步块之前,没有其他线程可以获取它。这就是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 类不关心调度程序如何暂停/恢复,并且没有任何同步块。

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

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

【讨论】:

    【解决方案2】:

    您不能只打电话给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 逻辑。

    【讨论】:

      【解决方案3】:

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

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

      wait() 也一样。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多