今天听了马老师的生产者消费者问题,里面提到一些细节,以前写的时候没注意,特地记录一下:
class Pool<T> {
private final int MAX = 20;
private volatile int top = 0;
List<T> list = new ArrayList<>(MAX);
public synchronized void put(T e) throws InterruptedException {
//第一个要注意的问题,这里要用while进行判断。
while(top == MAX) {
wait();
}
list.add(top, e);
top++;
//第二个问题,这里要用notifyAll。
notifyAll();
}
public synchronized T get() throws InterruptedException {
while(top == 0) {
wait();
}
top--;
T t = list.get(top);//其实这里有内存泄漏,用remove(top)可能好点。
notifyAll();
return t;
}
public int getSize() {
return top;
}
}
以上代码中,要注意的地方有两点:
1.put和get时,判断栈是否为空或是否为满要用while而不能用if
举个反栗,如果使用if进行判断的话:
a:多个生产者要执行put方法,发现Pool已经满了,放不进去了,此时这些生产者均会进入wait状态;
b:此时有一个消费者消费了一个产品,并唤醒了生产者们;
c:生产者1获得锁,向Pool中放入一个产品,top变成了MAX;
d:不巧,生产者2接着获得锁,打算再放入一个产品,即执行list.add(top,e),这下完犊子了吧,放进去的东西超过MAX了。
所以要用while,当一个线程被唤醒后,不会立即对Pool执行修改,而是再次进行检测,某种程度上类似于Singleton的双重检测机制:
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
2.要用notifyAll而不是notify
举个反例,用notify而没有用notifyAll
似乎反例不太好举,马老师举得例子不太实际,他说:
消费者全wait了
生产者只有一个醒着的,现在Pool里有MAX-1个产品,那么该生产者生产完放入,唤醒了另一个生产者,
接下来,这俩生产者看到Pool满了,都相继进入wait,结果就是死锁。
我想来想去都想不通他说的这种:消费者都进入wait而生产者竟然没唤醒消费者的青黄是如何发生的。
不管则么说,用notifyAll肯定是最保险的。