使用“notify()”& “wait()”而不是“suspend()”和“resume()”来控制一个线程

时间:2012-11-11 18:41:05

标签: java multithreading applet synchronization synchronized

我正在尝试学习如何在java中暂停和恢复一个线程。我正在使用Appletimplements 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方法,但它仍然会抛出异常。

有人可以解释为什么这不起作用以及如何解决同步问题?很少有解释点可以提供很多帮助;)

3 个答案:

答案 0 :(得分:10)

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

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

synchronized(foo) {

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

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

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

foo.wait();

在其他人在notify上调用notifyAllfoo(或线程被中断)之前,此线程不会恢复。一旦发生这种情况,该线程将尝试重新获取foo的监视器,然后继续。请注意,如果任何其他线程正在等待获取监视器,那么它们可能首先进入 - 不能保证JVM将发出锁定的顺序。请注意,如果没有人调用wait()notifynotifyAll将永远等待。通常最好使用另一种形式的wait超时。当有人拨打notify / notifyAll或超时已过期时,该版本将被唤醒。

因此,您需要一个线程来执行等待,而另一个线程需要进行通知。 waitnotify都必须将监视器保存在他们试图等待或通知的对象上;这就是你看到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进入睡眠状态20ms。
  • 线程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实例上调用wait / notify。类似地,RepaintScheduler中的逻辑并不理想,但仅用于说明如何使用wait / notify。

答案 1 :(得分: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逻辑。

答案 2 :(得分:1)

我认为你必须在线程上同步才能调用wait和notify。尝试使用

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

wait()相同。