使用"notify()"和"wait()"代替"suspend()"和"resume()"来控制一个线程

Son*_*ngo 3 java applet multithreading synchronization synchronized

我正在尝试学习如何在java中暂停和恢复一个线程.我使用的Appletimplements 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 Code Online (Sandbox Code Playgroud)

线程的run方法:

public void run(){
  while(true){
    repaint();
    try{
      Thread.sleep(20);
    } catch(InterruptedException e) {
      e.printStackTrace();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Exception in thread "AWT-EventQueue-1" java.lang.IllegalMonitorStateException

笔记:

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

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

Cam*_*ner 10

你误解了它是如何wait()运作的.调用wait一个Thread对象不会暂停该线程; 它反而告诉当前正在运行的线程等待其他事情发生.为了解释原因,我需要稍微补充一下并解释一下究竟synchronized是做什么的.

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

synchronized(foo) {
Run Code Online (Sandbox Code Playgroud)

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

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

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

foo.wait();
Run Code Online (Sandbox Code Playgroud)

在其他人调用notifynotifyAll打开foo(或线程被中断)之前,此线程不会恢复.一旦发生这种情况,该线程将尝试重新获取监视器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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您的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();
}
Run Code Online (Sandbox Code Playgroud)

请注意,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.