【问题标题】:Java wait() does not get waked by notify()Java wait() 不会被 notify() 唤醒
【发布时间】:2014-07-09 13:33:34
【问题描述】:

你好,我已经调试了一整天的代码,但我就是看不出哪里有问题。

我在主线程上使用 SerialPortEventListener,在工作线程中我有一个客户端套接字与服务器通信。 由于在这个工作线程到达return 之后,我仍然需要在主线程中完成一些总结工作,我想创建一个在主线程中等待的“伪线程”,直到它从监听器 onEvent 方法得到通知。

但是这个伪线程似乎永远在等待。

我检查了锁定线程pseudoThread,它们在 Runnable 和 Listener 类中应该具有相同的对象 ID。

显示“PseudoThread waiting”,但从未显示 PseudoThread awake。

控制台输出显示: 伪线程等待 .. .. 错误通知伪线程。

PS 如果我在 Main 类中使用 public final Object lock = new Object(); 创建一个锁并将所有 main.pseudoThread 替换为 main.lock,我会得到 java.lang.IllegalMonitorStateException。

private class Pseudo implements Runnable{
    Main main;
    public Pseudo(Main main) {
        this.main = main;
    }

    @Override
    public void run() {
        synchronized(main.pseudoThread){
            try {
                System.out.println("PseudoThread waiting");
                main.pseudoThread.wait();
                System.out.println("PseudoThread awake");
            } catch (InterruptedException e) {
                e.printStackTrace();
                return;
            }
        }

    }

}

在主方法中:

public static void main(String[] args) {
    Main main = new Main();
    main.initArduino();
    //more code. including starting the working thread
    main.pseudoThread = new Thread(main.new Pseudo(main));
        main.pseudoThread.start();
        try {
            main.pseudoThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
}
private void initArduino() {
    arduino = new Arduino(this);
    if(!arduino.initialize())
        System.exit(1);
}

在监听器类中(也在主线程中运行)

//class constructor;
public Arduino(Main Main){
    this.main = Main;
}
//listening method
public void serialEvent(SerialPortEvent oEvent){
    //some code to interract with working thread.
    record();

}
private void record(){
        synchronized(main.pseudoThread){
            main.pseudoThread.notify();
            System.out.println("notified pseudothread.");
        }
}

【问题讨论】:

  • 你为什么要调用interrupt()而不是notify();
  • 抱歉,现已更正。我试图通过中断解决这个问题..
  • 旁注:在线程实例本身上同步是不好的.. --> synchronized(main.pseudoThread)
  • 这段代码编译得好吗?您不能在静态上下文中使用它。 arduino = new Arduino(this);
  • 对不起,我修改了我的原始代码以使其更紧凑,但似乎我弄错了。是的,它编译得很好。

标签: java multithreading concurrency


【解决方案1】:

如果不深入研究可能实际发生的事情,我可以看出您对 wait()/notify() 的使用都是错误的。您可能正在经历“丢失的通知”。如果在调用时没有线程等待它,则 notify() 函数会执行 nothing。如果您的 serialEvent() 函数在其他线程调用 wait() 之前调用 notify(),则通知将丢失。

考虑这个例子:

class WaitNotify() {
    private final Object lock = new Object();
    private long head = 0;
    private long tail = 0;

    public void consumer() {
        synchronized (lock) {
            while(head == tail) {
                lock.wait();
            }
            doSomething();
            count head += 1;
        }
    }

    public void producer() {
        synchronized (lock) {
            tail += 1;
            lock.notify();
        }
    }
}

要点是:

(1) consumer() 函数等待数据之间的某种关系变为真:这里,它等待head != tail

(2) consumer() 函数循环等待。这有两个原因: (a) 许多程序有不止一个消费者线程。如果消费者 A 从 wait() 中醒来,则不能保证消费者 B 尚未声明他们都在等待的任何东西。并且 (b) Java 语言规范允许 foo.wait() 有时返回,即使 foo.notify() 没有被调用。这就是所谓的“虚假唤醒”。允许虚假唤醒(只要它们不经常发生)可以更轻松地实现 JVM。

(3) 锁对象是程序用来保护条件所依赖的变量的相同锁。如果这个例子是一个更大的程序的一部分,你会看到 synchronized(lock) 围绕着 head 和 tail 的每次使用,无论同步代码是 wait()ing 还是 notify()ing。

如果您自己的代码在调用 wait() 和 notify() 时遵守上述所有三个规则,那么您的程序将更有可能按照您期望的方式运行。

【讨论】:

  • 您好,感谢您的回复。但我使用wait()notify() 的目标不是同步任何关键对象,而是简单地保持整个程序运行直到收到通知。
  • 另外我不明白为什么如果我将 Main 类中声明的对象锁定为字段,编译器会给出 IllegalMonitorStateException。也许它暗示了我的错误?
  • @yfi,IllegalMonitorStateException 意味着您从一个 没有 锁定 foo 的线程调用了 foo.wait() 或 foo.notify()。据我所知,Java 库不会因为任何其他原因抛出该异常。
  • @yfi,你没有理解我的意思。是否存在“关键对象”无关紧要,但如果要等待(),则必须在循环中等待某些对象更改状态;如果你想通知一个等待的线程,你必须在调用 notify() 之前改变同一个对象的状态。 “状态”可以像布尔标志一样简单,但它必须是您可以在 while 循环中测试的东西。这就是 wait() 和 notify() 的设计用途。
  • 我必须使用while还是wait()和notify()的常用用法?在这里我只想阻止伪线程以保持整个程序运行(我假设否则,程序将在工作线程结束后完成)。至于IllegalMonitorException,我觉得很奇怪,因为我确实有synchronized(lock),其中锁是同一个对象,在两个线程中
【解决方案2】:

正如 james 所建议的,它可能会丢失通知情况,或者可能是......两个线程 1-您的主线程和 2-伪线程正在等待同一个线程实例锁 (main.pseudoThread)(Main线程通过调用join方法等待同一个锁)。 现在您正在使用 notify 从 join 方法中唤醒主线程,而不是那个 等待你的伪。要检查第二种情况,请尝试在记录中调用 notifyall 这将 确认第二种情况或将排除这种可能性。

无论如何,请重构您的代码,不要在 Thread 实例上使用同步,这是不好的做法。去 ReentrantLock 或 CoundDownLatch 吧。

【讨论】:

  • 谢谢!我会试试 notifyall
  • 你是对的!我错过了join也在等待的点,更改为notifyall后,伪线程现在可以被唤醒。你能否解释一下为什么在线程实例上同步是一个坏习惯? @TheLostMind 之前提到过,但没有详细说明
  • 1.为了让join起作用,每当一个线程死亡(即完成它的运行方法)时,它都会执行一个notifyAll。因此,如果您在线程实例上进行同步,那么即使您不期望任何通知,您也会收到通知。试试运行这段代码 -> pastebin.com/SWytLLZQ 你会明白的
  • 2.要么使用新的 Lock 接口来处理并发以获得更好的设计,要么使用“final Objects obj = new Object()”作为您的锁定机制(这种方法在某些情况下可能不可行)。最好使用 ReentrantLock、CountDownLatch 和并发数据结构进行并发
【解决方案3】:

notifywait 的用法似乎不正确。方法名称 notify 可能有点误导,因为它不是用于通用“通知”。这些方法用于控制同步块的执行。等待将允许其他线程在当前线程暂停时与同一对象同步。基本上,当某些资源不可用且执行无法继续时使用。另一方面,在通知线程完成其同步块后,通知将唤醒一个等待线程从等待中唤醒。同一对象的同步块中只能有一个线程同时存在。

如果这个想法只是保持主程序运行直到收到通知,那么信号量会更合适。像这样的。

public void run() {
   System.out.println("PseudoThread waiting");
   main.semaphore.acquireUninterruptibly();
   System.out.println("PseudoThread awake");
}
//...
private void record(){
   main.semaphore.release();
}
//...
public static void main(String[] args) {
   main.semaphore = new Semaphore(0);
   //...
}

【讨论】:

  • 但是我读了notify和wait只是binare Semaphor,所以我不明白优势在哪里。另一个注意事项:我的主线程是一个监听器,aquireUninterruptibly(甚至获取自身)不会被监听器方法阻塞吗?在这一点上,我注意到在我自己的代码中join() 似乎也阻塞了侦听器线程..
  • 通知和等待不是信号量,它们是互斥(互斥)块实现的重要组成部分。它用于确保只有一个进程执行受保护的代码。通知和等待用于避免轮询某些条件,而是允许给定进程进入睡眠状态并唤醒它们。可以使用互斥量、通知和等待来实现信号量。
  • 当您使用等待时,主线程已经阻塞。倾听的整个想法(在这种情况下)是等待某事发生。信号量就是这样做的。 record(..) 方法将引发信号量/释放,以便监听线程可以唤醒。
  • 与互斥锁相比,优势(在这种情况下)是信号量更适合这项工作。它更不容易出错,也更容易按照想要的方式进行工作。在其他一些情况下,情况可能会有所不同。
猜你喜欢
  • 2017-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多