【问题标题】:Notify seems to be waking up more than one thread通知似乎唤醒了多个线程
【发布时间】:2017-02-09 14:50:09
【问题描述】:

我正在使用wait()notify() 编写示例程序,但是当调用notify() 时,会唤醒多个线程而不是一个。

代码是:

public class MyQueue<T> {

    Object[] entryArr;
    private volatile int addIndex;

    private volatile int pending = -1;
    private final Object lock = new Object();

    private volatile long notifiedThreadId;
    private int capacity;

    public MyQueue(int capacity) {
        entryArr = new Object[capacity];
        this.capacity = capacity;
    }

    public void add(T t) {
        synchronized (lock) {
            if (pending >= 0) {
                try {
                    pending++;
                    lock.wait();
                    System.out.println(notifiedThreadId + ":" + Thread.currentThread().getId());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else if (pending == -1) {
                pending++;
            }
        }

        if (addIndex == capacity) { // its ok to replace existing value
            addIndex = 0;
        }

        try {
            entryArr[addIndex] = t;
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("ARRAYException:" + Thread.currentThread().getId() + ":" + pending + ":" + addIndex);
            e.printStackTrace();
        }

        addIndex++;

        synchronized (lock) {
            if (pending > 0) {
                pending--;
                notifiedThreadId = Thread.currentThread().getId();
                lock.notify();
            } else if (pending == 0) {
                pending--;
            }
        }
    }

}

public class TestMyQueue {

    public static void main(String args[]) {
        final MyQueue<String> queue = new MyQueue<>(2);

        for (int i = 0; i < 200; i++) {
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < Integer.MAX_VALUE; i++) {
                        queue.add(Thread.currentThread().getName() + ":" + i);
                    }
                }
            };
            Thread t = new Thread(r);
            t.start();
        }
    }

}

一段时间后,我看到两个线程被单线程唤醒。输出如下:

91:114
114:124
124:198
198:106
106:202
202:121
121:40
40:42
42:83
83:81
81:17
17:189
189:73
73:66
66:95
95:199
199:68
68:201
201:70
70:110
110:204
204:171
171:87
87:64
64:205
205:115

这里我看到115线程通知了两个线程,84线程通知了两个线程;因此,我们看到了ArrayIndexOutOfBoundsException

115:84

115:111

84:203

84:200

ARRAYException:200:199:3

ARRAYException:203:199:3

程序有什么问题?

【问题讨论】:

  • 您似乎错过了synchronized 的实际用途。这是为了保护对共享资源的访问,而不是在访问完全不受保护的共享资源时执行等待和通知。此外,你应该仔细阅读Object.wait() 的文档,尤其是“…spurious wakeups are possible, and this method should always be used in a loop”部分。
  • 感谢您的快速回复。我知道我们可以使用并发锁。但我的任务是使用wait() 和notify() 进行锁定。所以在任何时候,只有一个线程应该执行同步块之间的代码。
  • 我在评论中的什么地方提到了“并发锁”? synchronized必须跨越整个操作,包括对共享数据结构的每次访问,而不仅仅是执行waitnotify 的部分。您正在访问 synchronized 块之外的 entryArraddIndex 并将 addIndex 声明为 volatile 没有帮助,因为它不会使更新成为原子。

标签: java multithreading wait notify


【解决方案1】:

程序有什么问题?

您的代码有几个问题可能会导致此行为。首先,正如@Holder 所评论的,有很多可以由多个线程同时运行的代码段应该使用synchronized 块来保护。

例如:

if (addIndex == capacity) {
    addIndex = 0;
}

如果多个线程运行此操作,则多个线程可能会看到addIndex == capacity,并且多个线程将覆盖第 0 个索引。另一个例子是:

addIndex++;

如果 2 个线程尝试同时执行此语句,这是一个经典的竞争条件。如果addIndex 事先为0,则在2 个线程执行此语句后,addIndex 的值可能为1 或2,具体取决于竞争条件。

任何可以由多个线程同时执行的语句都必须正确锁定在synchronized 块内或以其他方式保护。即使您有 volatile 字段,仍然可能存在竞争条件,因为正在执行多个操作。

另外,一个典型的错误是在检查阵列上的溢出或不足流时使用if 语句。它们应该是while 语句,以确保您没有类消费者生产者竞争条件。请参阅my docs here 或查看相关的 SO 问题:Why does java.util.concurrent.ArrayBlockingQueue use 'while' loops instead of 'if' around calls to await()?

【讨论】:

    猜你喜欢
    • 2012-05-27
    • 2020-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-25
    • 2018-11-23
    相关资源
    最近更新 更多