使用“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 Runnablehas 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.

一旦你有了监视器,在你退出同步块之前,没有其他线程可以获取它.这就是 waitnotify 的用武之地.

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 上调用 notifynotifyAll (或线程被中断)之前,该线程不会恢复.一旦发生这种情况,该线程将尝试重新获取 foo 的监视器,然后继续.请注意,如果任何其他线程正在等待获取监视器,那么它们可能会先进入 - 无法保证 JVM 分发锁的顺序.请注意,如果没有人调用 notifynotifyAllwait() 将永远等待.通常最好使用需要超时的其他形式的 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.

因此,您需要一个线程来执行等待,而另一个线程来执行通知.waitnotify 都必须将监视器保持在它们试图等待或通知的对象上;这就是您看到 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.

相关文章