我正在尝试使用Java同步“原语”(synchronized,wait(),notify())实现类似于Java有界BlockingQueue接口的东西,但我遇到了一些我不理解的行为。
我创建了一个能够存储1个元素的队列,创建了两个线程等待从队列中获取值,启动它们,然后尝试在主线程的同步块中将两个值放入队列。大多数时候它可以工作,但有时等待值的两个线程似乎会相互唤醒,不允许主线程进入同步块。
这是我的(简化后的)代码:
然而,如果我将addWhenHasSpace和getWhenNotEmpty方法中while(...)循环内的notify()调用更改为notifyAll(),它就“总是”通过了。
我的问题是:为什么在这种情况下notify()和notifyAll()方法之间的行为会有所不同,以及为什么notify()的行为是这样的?
我希望在这种情况下两种方法的行为方式相同(两个线程WAITING,一个BLOCKED),因为:
1. 在这种情况下,似乎notifyAll()只会唤醒另一个线程,与notify()相同; 2. 看起来,选择唤醒线程的方法会影响被唤醒的线程(变为RUNNABLE)和主线程(已被BLOCKED)稍后竞争锁的方式——这也不是我从javadoc以及在网上搜索该主题时所期望的。
或者我完全做错了什么?
我创建了一个能够存储1个元素的队列,创建了两个线程等待从队列中获取值,启动它们,然后尝试在主线程的同步块中将两个值放入队列。大多数时候它可以工作,但有时等待值的两个线程似乎会相互唤醒,不允许主线程进入同步块。
这是我的(简化后的)代码:
import java.util.LinkedList;
import java.util.Queue;
public class LivelockDemo {
private static final int MANY_RUNS = 10000;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < MANY_RUNS; i++) { // to increase the probability
final MyBoundedBlockingQueue ctr = new MyBoundedBlockingQueue(1);
Thread t1 = createObserver(ctr, i + ":1");
Thread t2 = createObserver(ctr, i + ":2");
t1.start();
t2.start();
System.out.println(i + ":0 ready to enter synchronized block");
synchronized (ctr) {
System.out.println(i + ":0 entered synchronized block");
ctr.addWhenHasSpace("hello");
ctr.addWhenHasSpace("world");
}
t1.join();
t2.join();
System.out.println();
}
}
public static class MyBoundedBlockingQueue {
private Queue<Object> lst = new LinkedList<Object>();;
private int limit;
private MyBoundedBlockingQueue(int limit) {
this.limit = limit;
}
public synchronized void addWhenHasSpace(Object obj) throws InterruptedException {
boolean printed = false;
while (lst.size() >= limit) {
printed = __heartbeat(':', printed);
notify();
wait();
}
lst.offer(obj);
notify();
}
// waits until something has been set and then returns it
public synchronized Object getWhenNotEmpty() throws InterruptedException {
boolean printed = false;
while (lst.isEmpty()) {
printed = __heartbeat('.', printed); // show progress
notify();
wait();
}
Object result = lst.poll();
notify();
return result;
}
// just to show progress of waiting threads in a reasonable manner
private static boolean __heartbeat(char c, boolean printed) {
long now = System.currentTimeMillis();
if (now % 1000 == 0) {
System.out.print(c);
printed = true;
} else if (printed) {
System.out.println();
printed = false;
}
return printed;
}
}
private static Thread createObserver(final MyBoundedBlockingQueue ctr,
final String name) {
return new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(name + ": saw " + ctr.getWhenNotEmpty());
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
}
}, name);
}
}
当它“阻塞”时,这是我看到的:
(skipped a lot)
85:0 ready to enter synchronized block
85:0 entered synchronized block
85:2: saw hello
85:1: saw world
86:0 ready to enter synchronized block
86:0 entered synchronized block
86:2: saw hello
86:1: saw world
87:0 ready to enter synchronized block
............................................
..........................................................................
..................................................................................
(goes "forever")
然而,如果我将addWhenHasSpace和getWhenNotEmpty方法中while(...)循环内的notify()调用更改为notifyAll(),它就“总是”通过了。
我的问题是:为什么在这种情况下notify()和notifyAll()方法之间的行为会有所不同,以及为什么notify()的行为是这样的?
我希望在这种情况下两种方法的行为方式相同(两个线程WAITING,一个BLOCKED),因为:
1. 在这种情况下,似乎notifyAll()只会唤醒另一个线程,与notify()相同; 2. 看起来,选择唤醒线程的方法会影响被唤醒的线程(变为RUNNABLE)和主线程(已被BLOCKED)稍后竞争锁的方式——这也不是我从javadoc以及在网上搜索该主题时所期望的。
或者我完全做错了什么?
notify()
和wait()
?很可能你想要两个监视器——一个是"有东西可以消费"的,另一个是"有空间可以填充"的。 - Jon Skeet