最近一个朋友在写一个简单的生产者和消费者模型的时候,遇到了一个诡异的问题,他实用ReentrantLock 的lock去锁住生成者和消费者,并且通过Condition的await方法使生成者在临界值的时候,处于睡眠状态。但是在他的电脑上居然出现了消费者同步的问题,经过分析是其中一个消费者从await中异常被唤醒,而代码此时并没有调用signal
代码如下:
package com.thread;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyBlocking {
private static List<Object> list = new ArrayList<>();
private static Lock lock = new ReentrantLock();
private static Condition producer = lock.newCondition();
private static Condition consumer = lock.newCondition();
public static void main(String[] args) {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1; i++) {
Producer producer = new Producer();
newFixedThreadPool.execute(producer);
}
for (int i = 0; i < 10; i++) {
Consumer consumer = new Consumer();
newFixedThreadPool.execute(consumer);
}
}
static class Producer implements Runnable {
public void run() {
while (true) {
try {
lock.lock();
if (list.size() >= 1) {
try {
// 释放锁,当前线程挂起
producer.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 生产数据
list.add("1");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "生产了一个产品,当前队列的长度=" + list.size());
// 通知其他线程争抢cpu资源`
consumer.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 解除锁
lock.unlock();
}
}
}
}
static class Consumer implements Runnable {
public void run() {
while (true) {
try {
lock.lock();
int size = list.size();
if (size == 0) {
System.out.println("消费者线程挂起..." + Thread.currentThread().getName());
consumer.await();
System.out
.println("消费者线程复活..." + Thread.currentThread().getName() + ",list.size=" + list.size());
}
int size1 = list.size();
list.remove(size1 - 1);
System.out
.println("消费者线程" + Thread.currentThread().getName() + "正常消费一个...,list.size=" + list.size());
producer.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
}
代码比较简单就不做特别的描述了,就是实现了一个生产者和消费者同步的一个过程,这里因为使用了ReentrantLock对生产者和消费者作用一个lock 因此这里不需要考虑集合的线程安全性。
但是就是这么一段代码居然在不同的机器上表现出不同的问题,在我的设备上运行一切正常,但是在他的机器上确没执行多久就在这段代码上出现了问题,消费者消费完成以后 从集合里面删除内容。
list.remove(size1 - 1);
按照正常的思考来说,这个问题是不应该发生的因为,前面有对集合size的判断,当size为0的时候 所有消费者都进入了await(线程进入睡眠释放lock锁)
经过分析-就是在awiat的休眠时,当一个消费者别挂起的时候,另外一个消费者这时候被唤醒;我们常规的可能认为消费者别唤醒在这里只有生成者那边生成完成以后 调用signal通知消费者进行FIFO的被唤醒,其实不然这里就出了异常
那既然知道是await的问题,那我们就看下官方文档的描述如下
看完文档我们就恍然大悟了,还真有异常唤醒的这样的关键字。这就让我们想到了Object的wait方法一样,只是wait方法被异常唤醒的时候会执行InterruptedException,而这里await被异常唤醒的时候异常被系统处理了而已
public final void wait() throws InterruptedException {
wait(0);
}