当前位置: 首页 > 面试题库 >

使用“ notify()”和“ wait()”代替“ suspend()”和“ resume()”来控制线程

蔺山
2023-03-14
问题内容

我正在尝试学习如何在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);                               
}

线程的运行方法:

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()而不是同步。我尝试将单词添加synchronizedactionPerformed方法中,但仍会引发异常

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


问题答案:

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

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

synchronized(foo) {

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

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

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

foo.wait();

该线程将不会继续,直到其他人调用notifynotifyAll打开foo(或线程被中断)。一旦发生这种情况,该线程将尝试重新获取监视器foo,然后继续。请注意,如果有任何其他线程在等待获取监视器,那么它们可能会首先进入-
无法保证JVM将分发锁的顺序。请注意,wait()如果没有人致电notify或,它将永远等待notifyAll。通常最好使用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进入睡眠状态20毫秒。
  • 线程B(事件分发线程)收到按钮单击;称为“暂停”。
  • 线程B在LOCK上获取监视器。
  • 线程B更新’paused’变量并调用LOCK.notifyAll。
  • 没有线程在等待LOCK,因此没有发生任何有趣的事情。
  • 线程B在LOCK上释放监视器。
  • 线程A唤醒,再次经历其循环。
  • 线程A在LOCK上获取监视器。
  • 线程A认为应该暂停它,因此它调用LOCK.wait。
  • 此时,线程A挂起,等待有人调用notifyAll。线程A在LOCK上释放监视器。
  • 一段时间后,用户单击“恢复”。
  • 线程B调用scheduler.resume。
  • 线程B在LOCK上获取监视器。
  • 线程B更新’paused’变量并调用LOCK.notifyAll。
  • 线程A看到“ notifyAll”并唤醒。它试图获取LOCK上的监视器,但是它由线程B保留,因此线程A阻塞了。
  • 线程B在LOCK上释放监视器。
  • 线程A获取监视器并继续。

这一切有意义吗?

不需要单独的LOCK变量;我这样做是为了突出您没有在Thread实例上调用wait /
notify的事实。同样,RepaintScheduler中的逻辑也不理想,而仅仅是说明如何使用等待/通知。



 类似资料:
  • 我有个问题。当我在synchronized块中使用时,我有IllegalMonitorStateException。有谁能帮我解决这个问题吗? 我必须这样做,一个线程将发送到第二个线程char,然后这个线程必须等待和第二个线程打印这个char。在第二个线程等待之后,第一个线程再次发送下一个字符 main.java:

  • 我正在学习 处等待(由于< code > synchronized ,它从未进入其代码块)。一旦生产者线程退出它的同步代码块,消费者线程将进入它的。现在,队列是非空的,因为生产者只是在通知之前放了一些东西进去。消费者线程将移除它,调用notify,退出它的块,此时生产者将获得锁,因为它现在已经在生产者函数中的< code>synchronized(lock)行等待。三个问题: > < li> 在我

  • 我试图做到这一点:创建两个不同的线程,一个打印奇数,一个打印偶数。一旦一个线程打印出一个数字,它就必须等待另一个线程,以此类推,这是一个接一个的。 为了实现这一点,我将使用synchronized block以及wait()和notify()。 我正在创建一个类,该类的对象将用于传递给两个线程中的同步块。 -- --- SyncObject作为参数传递给main中的这些不同线程。 但是,此程序会停

  • 问题内容: 似乎该线程都在其他线程调用或在此线程上唤醒。两者之间有什么区别吗? - 编辑 - 我知道一个是通知对象,另一个是中断线程。但是,这两种情况都会导致相同的结果,也就是说,该线程被唤醒,所以我想问的是这两种情况的后果是如何不同的。 问题答案: 当线程在某个监视器上调用notify时,它将唤醒在该监视器上等待的单个线程,但是 哪个 线程被唤醒由调度程序决定。(或者,一个线程可以调用notif

  • 问题内容: 我有很大一部分不是循环的代码,只是发生一次但要花费一些时间的命令列表。我需要它根据更改的布尔值在任何时候暂停或终止此操作。我可以使用其他线程来挂起,恢复和停止此代码,但是不赞成使用这些方法,因此我想避免使用它们。我可以检查每行代码之间的布尔值,但我希望有一个更优雅的解决方案。有什么好方法吗? 问题答案: 自然,使用来处理中断线程的正确方法(在这种情况下,是暂停或停止线程)。它的设计目的

  • 问题内容: 我需要使线程顺序。他们需要按以下顺序启动: 当D完成时,C可以完成,然后B,然后是A。 在这种情况下,最好使用线程或?为什么呢? 我的线程需要启动并打印消息,完成后需要打印。 问题答案: 由于您正在等待“其他”线程完成(即完成执行),因此是更好的选择。 的javadoc 简单地说: 等待该线程死亡。 然后,该机制相对简单: 要说明:您需要参考。因此,指向,指向,指向和不指向任何对象(它