Notify似乎正在唤醒多个线程

时间:2017-02-09 14:50:09

标签: java multithreading wait notify

我正在使用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

该计划有什么问题?

1 个答案:

答案 0 :(得分: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()?