虚假唤醒

虚假唤醒


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();
    }
}
  • jdk官方文档

  • 当有更多线程时就会出现虚假唤醒,把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
 */
posted @ 2021-09-28 16:02  n1ce2cv  阅读(446)  评论(0编辑  收藏  举报