notifyAll()不通知所有线程

时间:2017-07-20 22:56:01

标签: java multithreading java-threads

这是我的代码和输出在我运行代码的任何时候都是不同的。有时会通知所有三位读者并输出:

等待计算...

等待计算...

等待计算...

成品

总计是: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之间存在差异,甚至进一步问题。

3 个答案:

答案 0 :(得分:1)

我能够重现这个问题 - 基本上,如果你的计算机太快或计划不好,计算器有可能在读者有机会同步和等待之前完成。

  

c:\ files \ j&gt; java Reader
  等待计算...
  等待计算...
  完成了   等待计算...
  总计:4950Thread-2
  总计是:4950Thread-0

为了防止这种情况发生,您应该在执行计算之前验证所有读者是否已准备就绪。

c:\ files \ j&gt; java Reader 等待计算...... 等待读者...目前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();
    }
}

答案 1 :(得分:0)

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

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

问题的一个可能解决方案是使用CountDownLatch,让你让计算器等到所有三个读者都准备就绪。

答案 2 :(得分:0)

让您的主要方法检查其他读者的状态。

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();
}

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