虚假唤醒
class A {
public static void main(String[] args) {
Data data = new Data();
// +1
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
// -1
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
}
}
class Data {
private int num = 0;
// +1
public synchronized void increment() throws InterruptedException {
if (num != 0) {
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "->" + num);
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
if (num == 0) {
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "->" + num);
this.notifyAll();
}
}
- 当有更多线程时就会出现虚假唤醒,把if改成while避免虚假唤醒。等待应该总是出现在循环中
class A {
public static void main(String[] args) {
Data data = new Data();
// 生产者
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
data.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
// 生产者
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
data.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
// 消费者
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(10);
data.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
// 消费者
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(20);
data.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
}
}
class Data {
private int num = 0; // 当前缓冲池中数量
private int maxSize = 3; // 缓冲池最大放3个
// 生产一个
// 关键字在实例方法上,锁为当前实例(当关键字在静态方法上,锁为当前Class对象)
public synchronized void produce() throws InterruptedException {
// 出现虚假唤醒的原因是从阻塞态到就绪态再到运行态没有进行判断,需要让其每次得到操作权时都进行判断
if (num >= maxSize) { // 改成while缓冲池就不会溢出
System.out.println(Thread.currentThread().getName() + "被阻塞在wait()");
this.wait(); // 提前释放synchronized锁,重新去请求锁导致的阻塞
}
num++;
System.out.println(Thread.currentThread().getName() + "生产1个后剩余" + num);
this.notifyAll(); // notify()或者notifyAll()方法并不是真正释放锁,必须等到synchronized方法或者语法块执行完才真正释放锁
}
// 消费1个
public synchronized void consume() throws InterruptedException {
if (num <= 0) { // 改成while缓冲池就不会溢出
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "消费1个后剩余" + num);
this.notifyAll();
}
}
/*
// 如果一开始是消费者抢到锁,由于num=0,会执行wait()。被阻塞在wait(),进入等待队列。
A生产1个后剩余1 // 生产者A先抢到锁,开始生产。(BCD试图竞争锁,都会被阻塞在同步代码块开始处,进入等待队列)
// 生产一个后调用notifyAll()唤醒在等待队列中的消费者CD和生产者B,BCD进入同步队列。
// 同步代码块执行完synchronized会自动释放锁。同步队列中ABCD一起竞争锁。
A生产1个后剩余2 // 又是A抢到锁,BCD还是阻塞在各自同步代码块开始处,进入等待队列。
// 生产一个后notifyAll()唤醒在等待队列中的消费者CD和生产者B,BCD进入同步队列。
// 同步代码块执行完synchronized会自动释放锁。同步队列中ABCD一起竞争锁。
A生产1个后剩余3 // 又是A抢到锁,BCD还是阻塞在各自同步代码块开始处,进入等待队列。
// 生产一个后notifyAll()唤醒在等待队列中的消费者CD和生产者B,BCD进入同步队列。
// 同步代码块执行完synchronized会自动释放锁。同步队列中ABCD一起竞争锁。
// 缓冲区满。若生产者A(B同理)抢到锁,则执行wait(),提前释放锁,自己被阻塞在wait()处,进入等待队列。
// 然后notifyAll()唤醒在等待队列中的消费者CD和生产者B,BCD进入同步队列去竞争锁。
// 如果此时生产者B去竞争锁,也会因为num >= maxSize而调用wait()进入等待队列,这次被阻塞在wait()处。
A被阻塞在wait() // AB都阻塞在wait()
B被阻塞在wait()
C消费1个后剩余2 // 在同步队列中的消费者CD去竞争锁,C抢到,D阻塞在同步代码块开始处,进入等待队列。
// C消费一个后去notifyALl()唤醒了ABD。并在同步代码块结束后释放锁。
// 同步队列中ABCD一起竞争锁。
A生产1个后剩余3 // A抢到锁,BCD阻塞在原来位置(B在wait()处,CD在同步代码块开始处),进入等待队列。
// 生产一个后notifyAll()唤醒在等待队列中的消费者CD和生产者B,BCD进入同步队列。
// 同步代码块执行完synchronized会自动释放锁。同步队列中ABCD一起竞争锁。
A被阻塞在wait() // 缓冲区满。A又抢到了锁,因为num >= maxSize被阻塞在wait()
B生产1个后剩余4 // 生产者B抢到锁,从之前被阻塞的wait()往后执行,不经过if判断,num+1。(此时CD阻塞在各自同步代码块处,A在wait())
// 然后notifyAll()唤醒在等待队列中的消费者CD和生产者A,ACD进入同步队列去竞争锁。
B被阻塞在wait() // B又抢到了锁,因为num >= maxSize被阻塞在wait()。
A生产1个后剩余5 // A抢到锁,从wait()后直接执行,不经过if判断,num+1。
// 然后notifyAll()唤醒在等待队列中的消费者CD和生产者B,ABCD进入同步队列。
// 同步代码块执行完synchronized会自动释放锁。同步队列中ABCD一起竞争锁。
B生产1个后剩余6 // B抢到锁,从之前被阻塞的wait()往后执行,不经过if判断,num+1。(此时ACD都阻塞在同步代码块开始处)
B被阻塞在wait() // B抢到了锁,因为num >= maxSize被阻塞在wait()
// ......
C消费1个后剩余5
B生产1个后剩余6
B被阻塞在wait()
C消费1个后剩余5
B生产1个后剩余6
B被阻塞在wait()
C消费1个后剩余5
B生产1个后剩余6
C消费1个后剩余5
C消费1个后剩余4
C消费1个后剩余3
C消费1个后剩余2
C消费1个后剩余1
C消费1个后剩余0
*/