【问题标题】:notifyAll() does not notify all threadsnotifyAll() 不通知所有线程
【发布时间】:2017-07-20 22:56:00
【问题描述】:

这是我的代码,每次运行代码时输出都会有所不同。有时会通知所有三个读者并输出:

等待计算...

等待计算...

等待计算...

完成

总数为:4950Thread-1

总数为:4950Thread-2

总数为:4950Thread-0

有时只会通知两个或一个读者。 有什么问题?

class Reader extends Thread {
    Calculator c;

    public Reader(Calculator calc) {
        c = calc;
    }

    public void run() {
        synchronized (c) {
            try {
                System.out.println("Waiting for calculation...");
                c.wait();
            } catch (InterruptedException e) {
            }
            System.out.println("Total is: " + c.total +Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        new Reader(calculator).start();
        new Reader(calculator).start();
        new Reader(calculator).start();
        new Thread(calculator).start();
    }
}

class Calculator implements Runnable {
    int total;

    public void run() {
        synchronized (this) {
            for (int i = 0; i < 100; i++) {
                total += i;
            }
            System.out.println("Finished");
            notifyAll();
        }
    }
}

根据元帖子,这个问题声称是重复的,但是被欺骗的两个“重复”根本不适用。 How to use wait and notify in Java? 提醒用户,如果你真的想在同一个对象上等待,你必须在那个对象上进行同步。但是这个解决方案已经在这样做了。 Java: notify() vs. notifyAll() all over again 提醒用户notifynotifyAll 之间的区别,这甚至进一步问题。

【问题讨论】:

  • 在 MacOS/JDK 1.8 上为我工作!
  • Calculator 可以在 Reader 开始等待之前通知。
  • BTW 扩展 Thread 并不可取。
  • @JarrodRoberson 这不是一个重复的问题。该问题的答案已在他的问题中使用。相反,他正在这样做,但没有考虑线程调度程序的微妙复杂性。
  • 打开了一个元问题来解决这个问题 - meta.stackoverflow.com/questions/352598/…

标签: java multithreading java-threads


【解决方案1】:

我能够重现该问题 - 基本上,如果您的计算机速度太快,或者日程安排不当,计算器可能会在读者有机会同步和等待之前完成。

c:\files\j>java 阅读器
等待计算...
等待计算...
完成
等待计算...
总数为:4950Thread-2
总数为:4950Thread-0

为防止这种情况发生,您应该在执行计算之前确认所有阅读器都已准备就绪。

c:\files\j>java 阅读器 等待计算... 等待读者...目前 1 等待计算... 等待计算... 完成的 总数为:4950Thread-1 总数为:4950Thread-2 总数为:4950Thread-0

这是我的代码

import java.util.concurrent.atomic.AtomicInteger; // corsiKa added import
class Reader extends Thread {

    static class Calculator implements Runnable {
        int total;
        AtomicInteger readers = new AtomicInteger(0); // corsiKa added atomicinteger

        public void run() {
            // corsiKa added while
            while(readers.get() < 3) {
                System.out.println("Waiting for readers... currently " + readers.get());
                try { Thread.sleep(100); } catch(InterruptedException e) { }
            }
            synchronized (this) {
                for (int i = 0; i < 100; i++) {
                    total += i;
                }
                System.out.println("Finished");
                notifyAll();
            }
        }
    }

    Calculator c;

    public Reader(Calculator calc) {
        c = calc;
    }

    public void run() {
        synchronized (c) {
            try {
                c.readers.incrementAndGet(); // corsiKa added increment
                System.out.println("Waiting for calculation...");
                c.wait();
            } catch (InterruptedException e) {
            }
            System.out.println("Total is: " + c.total +Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        new Reader(calculator).start();
        new Reader(calculator).start();
        new Reader(calculator).start();
        new Thread(calculator).start();
    }
}

【讨论】:

  • 谢谢,我知道了。我应该在运行方法中的同步块之前添加睡眠方法
  • 没错!我正要上传一个例子。
  • 使用睡眠来解决这个问题是一个非常糟糕的主意。这降低了问题发生的可能性,但并未解决程序中明显的同步问题。
  • 好的,当将睡眠方法与原子整数结合使用时,这将解决问题。我之前的评论是指 cmets,而不是您的答案。但是,您最好使用 java.util.concurrent 包 imho 中的正确工具。
  • 确实,像CountDownLatch 这样的东西可以让恕我直言,比您在其中创建的当前 atomic+sleep 更清洁的解决方案。即使如果问题中的代码很差,你不认为值得 1. 尝试在“脏”环境中提供“最干净”的可能解决方案 2.指出如何制作现有的代码“更干净”?你知道......人们谷歌,来到这里,复制+粘贴这些东西。 (你很难怪他们)
【解决方案2】:

您的代码的问题是未定义线程的启动顺序。直观预期的行为是读者首先开始,然后是计算器。这正是您在上面显示的日志中发生的事情。

但也有其他可能的交错。调用Thread.start 并不能保证开始的顺序。假设两个读者首先开始,然后是计算器,然后是最后一个读者。在这种情况下,计算器可以在第三位读者之前进入临界区。计算器中的 notifyAll 调用随后发生在第三个读取器执行其等待调用之前。因此第三个读者该死地等待永远,因为不会在锁定对象上发生notifynotifyAll 的其他调用。

解决您的问题的一个可能方法是使用CountDownLatch,它允许您让计算器等到所有三个阅读器都准备好。

【讨论】:

    【解决方案3】:

    让你的 main 方法检查其他 Reader 的状态。

    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        Reader read1 = new Reader(calculator);
        read1.start();
        Reader read2 = new Reader(calculator);
        read2.start();
        Reader read3 = new Reader(calculator);
        read3.start();
    
        while(read1.getState() != Thread.State.WAITING && 
              read2.getState() != Thread.State.WAITING &&
              read3.getState() != Thread.State.WAITING){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
        new Thread(calculator).start();
    }
    

    这将确保您绝对让所有线程等待,并且每次都产生相同的结果。

    【讨论】:

      猜你喜欢
      • 2012-08-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-02
      • 2018-11-23
      相关资源
      最近更新 更多