1 何为虚假唤醒
当线程从等待状态中被唤醒时,只是发现未满足其正在等待的条件时,就会发生虚假唤醒。 之所以称其为虚假的,是因为该线程似乎无缘无故被唤醒。 虚假唤醒不会无缘无故发生,通常是因为在发起唤醒号和等待线程最终运行之间的临界时间内,线程不再满足竞态条件。
2 java中的例子
public class SpuriousWakeupRWLock {
private static final CustomQueue CUSTOM_LIST = new CustomQueue();
public static void main(String[] args) throws Exception {
for (int i = 0; i < 8; i++) {
Thread consumer = new Thread(() -> {
try {
CUSTOM_LIST.getOne();
} catch (InterruptedException e) {
e.printStackTrace();
}
;
});
consumer.setName("consumer:[" + i + "]");
consumer.start();
}
TimeUnit.SECONDS.sleep(8);
for (int i = 0; i < 2 ; i++) {
int finalI = i;
Thread producer = new Thread(() -> {
try {
CUSTOM_LIST.putOne(finalI);
} catch (InterruptedException e) {
e.printStackTrace();
}
;
});
producer.setName("producer : [" + i + "]");
producer.start();
}
}
static class CustomQueue {
private List<Integer> data = new ArrayList<>();
public synchronized void getOne() throws InterruptedException {
if (data.size() <= 0) {
System.out.println(Thread.currentThread() + " : wait");
TimeUnit.SECONDS.sleep(1);
this.wait();
}
Integer number = data.get(0);
data.remove(0);
System.out.println(Thread.currentThread() + "-----> get one :" + number);
this.notifyAll();
}
public synchronized void putOne(int number) throws InterruptedException {
if (data.size() == Integer.MAX_VALUE) {
this.wait();
}
data.add(number);
this.notifyAll();
System.out.println("put one :" + number);
}
}
}
启动8个消费者和两个生产者,运行结果如下:
Thread[consumer:[0],5,main] : wait
Thread[consumer:[7],5,main] : wait
Thread[consumer:[6],5,main] : wait
Thread[consumer:[5],5,main] : wait
Thread[consumer:[4],5,main] : wait
Thread[consumer:[3],5,main] : wait
Thread[consumer:[2],5,main] : wait
Thread[consumer:[1],5,main] : wait
put one :1
Exception in thread "consumer:[3]" Exception in thread "consumer:[4]" Exception in thread "consumer:[6]" Exception in thread "consumer:[5]" Exception in thread "consumer:[0]" Exception in thread "consumer:[7]" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
put one :0
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
Thread[consumer:[1],5,main]-----> get one :1
at java.util.ArrayList.get(ArrayList.java:433)
Thread[consumer:[2],5,main]-----> get one :0
at com.nicky.spurious.SpuriousWakeupRWLock$CustomQueue.getOne(SpuriousWakeupRWLock.java:59)
at com.nicky.spurious.SpuriousWakeupRWLock.lambda$main$0(SpuriousWakeupRWLock.java:20)
at java.lang.Thread.run(Thread.java:748)
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.get(ArrayList.java:433)
at com.nicky.spurious.SpuriousWakeupRWLock$CustomQueue.getOne(SpuriousWakeupRWLock.java:59)
at com.nicky.spurious.SpuriousWakeupRWLock.lambda$main$0(SpuriousWakeupRWLock.java:20)
at java.lang.Thread.run(Thread.java:748)
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.get(ArrayList.java:433)
at com.nicky.spurious.SpuriousWakeupRWLock$CustomQueue.getOne(SpuriousWakeupRWLock.java:59)
at com.nicky.spurious.SpuriousWakeupRWLock.lambda$main$0(SpuriousWakeupRWLock.java:20)
at java.lang.Thread.run(Thread.java:748)
.............
3 唤醒异常原因
3.1 执行流程
线程在调用getOne
方法的是时候,获取锁 –> 当前队列为空–> 进入wait状态 –> 释放锁,所有线程都进入等待状态,
这时候通过putOne
方法往队列里面添加元素 –> 唤醒线程 –> 线程直接往下运行 –> 异常
3.2 异常原因
产生原因是在线程从进入wait
状态时候的条件通过唤醒的时候,线程并未再次进入该条件:
if (data.size() <= 0) {
System.out.println(Thread.currentThread() + " : wait");
TimeUnit.SECONDS.sleep(1);
this.wait();
}
上述代码使用的是if条件语句会导致如果等待的线程被唤醒,不会再次执行if
的条件,直接往下执行,从而破坏了线程竞争资源的
竞态条件问题
解决方案是将if
条件语句修改为while
语句,如下
while (data.size() <= 0) {
System.out.println(Thread.currentThread() + " : wait");
TimeUnit.SECONDS.sleep(1);
this.wait();
}
此时,如果多个线程被唤醒,则会重新调用while
语句,从而再次竞争资源,结果如下:
Thread[consumer:[0],5,main] : wait
Thread[consumer:[7],5,main] : wait
Thread[consumer:[6],5,main] : wait
Thread[consumer:[5],5,main] : wait
Thread[consumer:[4],5,main] : wait
Thread[consumer:[3],5,main] : wait
Thread[consumer:[2],5,main] : wait
Thread[consumer:[1],5,main] : wait
put one :1
put one :0
Thread[consumer:[1],5,main]-----> get one :1
Thread[consumer:[2],5,main]-----> get one :0
Thread[consumer:[3],5,main] : wait
Thread[consumer:[4],5,main] : wait
Thread[consumer:[5],5,main] : wait
Thread[consumer:[6],5,main] : wait
Thread[consumer:[7],5,main] : wait
Thread[consumer:[0],5,main] : wait
JUC中的队列ArrayBlockingQueue就是使用的这种方式避免虚假唤醒,比如take
方法
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
[评论][COMMENTS]