java - 使用 "notify()"& "wait()"而不是 "suspend()"和 "resume()"来控制线程

标签 java multithreading applet synchronization synchronized

我正在尝试学习如何在 Java 中暂停和恢复线程。我正在使用一个Applet,它实现Runnable有两个按钮“Start”和“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();
    }
  }
}

现在每当我尝试暂停或恢复线程时都会抛出异常:

线程“AWT-EventQueue-1”java.lang.IllegalMonitorStateException 中的异常

注意事项:

如果我使用已弃用的方法 suspend()resume(),之前的代码运行完美,但文档指出使用 notify()wait() 而不是同步。我尝试将单词 synchronized 添加到 actionPerformed 方法中,但它仍然抛出异常。

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

最佳答案

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

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

synchronized(foo) {

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

一旦你拥有了监视器,在你退出同步块(synchronized block)之前,其他线程都无法获得它。这就是 waitnotify 发挥作用的地方。

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

foo.wait();

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

所以这里可能的事件顺序是:

  • 线程 A 开始运行重绘调度程序。
  • 线程 A hibernate 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 实例上调用 wait/notify 这一事实。同样,RepaintScheduler 内部的逻辑并不理想,但只是为了说明如何使用 wait/notify。

关于java - 使用 "notify()"& "wait()"而不是 "suspend()"和 "resume()"来控制线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13334207/

相关文章:

java - 线程中的异常 "main"java.lang.ArrayIndexOutOfBoundsException : 5

c++ - 如何构建等待来自不相关类的两个信号对象的代码?

java - 当线程调用 wait 时,它会释放锁而不是竞争条件

java - CMS 相对于 Java 的垃圾收集器意味着什么?

java - JUnit 似乎没有调用我重写的 TestWatcher 失败/成功方法

java - 编写 Util 类的最佳方式

java - 执行两个线程,一个等待另一个,而主线程继续

javascript 取消隐藏 iframe 内的小程序

Java Applet 未在 Eclipse 的内置 applet 查看器中更新

java - 在 Wordpress 中嵌入 Java 小程序