听网课时 视频里老师说wait()方法要放在while循环里面,否则会有虚假唤醒的情况出现,一脸懵逼。。。上网找文章看,然后二脸懵逼。
思考了半个小时 ,才有了点灵感。。
废话不多说 ,开整:
1.首先 创建一个容器BlocksContiner 成员变量,
Object[] array 存储有效元素 size 记录有效元素个数。
并为其创建放数据方法(put())和取数据方法 (take())并使用可重入锁。
class BlocksContiner<T>{
private Object[] array;//存储有效元素
private int size;//记录有效元素个数
//jdk 1.5 提供的一种可重入锁
private ReentrantLock lock=new ReentrantLock(true);
private Condition producerCondition=lock.newCondition();//生产者
private Condition consumerCondition=lock.newCondition();//消费者
public BlocksContiner() {
this(16);//默认大小为16
}
public BlocksContiner(int cap){
array=new Object[cap];
}
//同步方法
public void put(T t){
lock.lock();
try {
if(size==array.length){
try {
producerCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//放数据
array[size]=t;
//有效元素个数+1
size++;
//通知消费者线程取数据
consumerCondition.signalAll();
}finally {
lock.unlock();
}
}
/**
* 从容器中取数据 永远取第0个位置 FIFO
* @return
*/
public T take(){
lock.lock();
try {
//1.判定容器是否为空
if(size==0){
try {
//此处进入阻塞状态 假如一个线程进入阻塞状态
// 它就会放弃CPU 其他线程可以开始抢占CPU
consumerCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//2.取第0个位置的数据
Object result=array[0];
//3.移动元素
System.arraycopy(array, 1, array, 0, size-1);
//4.有效元素个数减一 ,size位置元素 设置为空
size--;
array[size]=null;
//5.通知等待线程(生产者)继续放数据
producerCondition.signalAll();
return (T) result;
}finally {
lock.unlock();
}
}
@Override
public String toString() {
return "BlockContiner{" +
"array=" + Arrays.toString(array) +
'}';
}
}
2.再创建Producer和Consumer 类
//生产者
class Producer extends Thread{
private BlocksContiner<Object> container;
public Producer(BlocksContiner<Object> container){
this.container=container;
}
@Override
public void run() {
int i =1;
while (true){
container.put(i);
i++;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Consumer extends Thread{
private BlocksContiner<Object> container;
public Consumer(BlocksContiner<Object> container){
this.container=container;
}
@Override
public void run() {
while (true) {
Object take = container.take();
System.out.println(take);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3.测试
public class TestCondition {
public static void main(String[] args) {
doMethod02();
}
private static void doMethod02() {
//1.构建一个Container 对象
BlocksContiner<Object> continer=new BlocksContiner<>();
//2.构建生产者/消费者对象关联当前容器
Producer producer=new Producer(continer);
Consumer consumer=new Consumer(continer);
Producer producer1=new Producer(continer);
Consumer consumer1=new Consumer(continer);
producer.start();
consumer.start();
producer1.start();
consumer1.start();
}
}
结果 如下图:
出现数组下标越界异常。
分析原因:这个代码启动了四个线程,两个生产者Producer,两个消费者Consumer,消费者线程调用容器的take方法,由于使用了可重入锁,所以每一次只有一个线程进行if判断,当第一个线程走到wait方法,此时wait方法会释放锁,并且在此阻塞,那么第二个和第三个线程也会相继执行到同一程度.
然后生产者线程添加一个有效元素到容器中,并且通知所有被锁住的消费者线程,假如第一个线程先恢复,抢到了锁,它会正确的take容器内的有效元素,之后释放锁,第二个线程抢到之后由于size–,当size为0时,必定报错.这就出现了过早唤醒的问题了。
其实就是 多个消费者线程执行到wait()上方进入阻塞状态 。被唤醒时 由于多个消费者线程已经进入了 if代码块中 所以会继续向下执行调用take()方法
导致虚假唤醒问题的出现。
4.解决方法 将if 替换为while,唤醒线程后会继续进行判断,也就是假如条件成立则一直处于循环,不会继续向下执行 。
5.新人小白一枚,如有问题请指正,提前在此拜谢。
这篇博客参考了很多大佬的文章 侵删。