Java虚假唤醒及如何避免虚假唤醒

Java虚假唤醒及如何避免虚假唤醒

先给出一段虚假唤醒的代码如下:

package bat.ke.qq.com.learnjuc.thread;

public class SpuriousWakeup {

    public static void main(String[] args) {
        Data2 data = new Data2();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生产者A").start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生产者B").start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者C").start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者D").start();
    }
}

//线程操作的资源类,判断等待业务、通知
class Data2 {
    private int number = 0;

    //+1
    public synchronized void increment() throws InterruptedException {
        if (number != 0) {
            //等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        //通知其他线程,我+1完毕了
        this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
        if (number == 0) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        //通知其他线程,我-1完毕了
        this.notifyAll();
    }


}



//执行以上的代码结果输出如下:
生产者A=>1 
消费者C=>0 
生产者B=>1 
生产者A=>2 
生产者B=>3
消费者C=>2
消费者C=>1
消费者C=>0
生产者B=>1
生产者A=>2
生产者B=>3
消费者C=>2
生产者B=>3
生产者A=>4
消费者D=>3
消费者D=>2
消费者D=>1
消费者D=>0
生产者A=>1
消费者D=>0


1、num=0 ,生产者A 拿到对象锁进入同步块中执行num++,num = 1,打印:生产者A=>1,

2、num=1,生产者A未释放对象锁又进入同步块中,但是此时num!=0,生产者A waiting在num=0 上,释放锁。 生产者A此时的状态为waiting状态,等待消费者唤醒它能够进入阻塞状态

3.num=1 ,生产者B拿到对象锁进入同步代码块中,但是此时num!=0,生产者B waiting在num=0 上,释放锁。 生产者B此时的状态为waiting状态,等待消费者唤醒它能够进入阻塞状态

a、b   waiting 。。。。。。。。。。。

4.num=1,消费者C拿到对象锁进入同步代码块中,执行num-- ,打印: 消费者C=>0 。

然后notifyall 生产者A、生产者B 。

生产者A、生产者B被消费者C唤醒,他们的的状态从waiting状态更改成block状态 (a、b waiting ==> a、b  blocking)。 在被唤醒的那一瞬间,几毫秒中, 生产者A、生产者B是竞争关系, 都在等待消费者C 释放锁,并且争夺对象锁。

5.消费者C重新进入代码块中,因为num=0 ,所以消费者c waiting了 ,(c running -》c waiting)

消费者C 释放锁,生产者B 抢到 对象锁 。。(b blocking -> b running, a blocking 保持不变,) 仔细看 生产者的同步块,

public synchronized void increment() throws InterruptedException {
if (number != 0) {
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我+1完毕了
this.notifyAll();
}

上述代码使用if判断,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码

生产者B 直接执行num++ 操作 ,打印:生产者B=>1, , 生产者B也唤醒了消费者c ,消费者从waiting变成blocing

c waiting -> c blocking

6.生产者B重新 进入同步块中,waiting在num!=0 上 ,生产者B释放锁

(b running-> b  waiting)

7.生产者A抢到 对象锁 。。(a blocking -> a running) 仔细看 生产者的同步块,

public synchronized void increment() throws InterruptedException {
if (number != 0) {
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我+1完毕了
this.notifyAll();
}

上述代码使用if判断,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码

生产者A 直接执行num++ 操作 ,打印:生产者A=>2, 注意: 此时生产者A 执行notify all ,生产者B被 唤醒了 。。。

(b waiting->blocking)

紧接着生产者A 重新进入同步块,此时num =2 ,生产者A的状态变为waiting(a running -> a blocking)

8.生产者B和消费者C 都是竞争对象锁,生产者B 很争气 又抢到了锁。 (b blocking->b running) 于是打印:生产者B=>3

9.接下来: b runing -》b waiting , c blocing -> c running ,所以呢打印:

消费者C=>2    消费者C=>1   消费者C=>0

  1. a、b waiting-》 a、b blocing ,

    选择a、b中的一个running。。。。。。。。。。。。。。。。。。。。。。

什么叫虚假唤醒?
站在上述两个消费者线程的角度上讲, 无论哪一个线程抢到了资源, 另一个线程的唤醒就可以被认为是没有必要的, 也就是被虚假唤醒了。

虚假唤醒发生的场景以及解决方式?
上述代码使用if判断,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。

解决虚假唤醒生产者消费者实例代码:

package bat.ke.qq.com.learnjuc.thread;

public class SpuriousWakeup {

    public static void main(String[] args) {
        Data2 data = new Data2();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生产者A").start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生产者B").start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者C").start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者D").start();
    }
}

//线程操作的资源类,判断等待业务、通知
class Data2 {
    private int number = 0;

    //+1
    public synchronized void increment() throws InterruptedException {
        while (number != 0) {//0
            //等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        //通知其他线程,我+1完毕了
        this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
        while (number == 0) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        //通知其他线程,我-1完毕了
        this.notifyAll();
    }


}

//执行以上的代码结果输出如下:
生产者A=>1
消费者C=>0
生产者B=>1
消费者C=>0
生产者A=>1
消费者C=>0
生产者B=>1
消费者C=>0
生产者A=>1
消费者C=>0
生产者B=>1
消费者D=>0
生产者A=>1
消费者D=>0
生产者B=>1
消费者D=>0
生产者A=>1
消费者D=>0
生产者B=>1
消费者D=>0

1、num=0 ,生产者A 拿到对象锁进入同步块中执行num++,num = 1,打印:生产者A=>1,

2、num=1,生产者A未释放对象锁又进入同步块中,但是此时num!=0,生产者A waiting在num=0 上,释放锁。 生产者A此时的状态为waiting状态,等待消费者唤醒它能够进入阻塞状态

3.num=1 ,生产者B拿到对象锁进入同步代码块中,但是此时num!=0,生产者B waiting在num=0 上,释放锁。 生产者B此时的状态为waiting状态,等待消费者唤醒它能够进入阻塞状态

a、b   waiting 。。。。。。。。。。。

4.num=1,消费者C拿到对象锁进入同步代码块中,执行num-- ,打印: 消费者C=>0 。

然后notifyall 生产者A、生产者B 。

生产者A、生产者B被消费者C唤醒,他们的的状态从waiting状态更改成block状态 (a、b waiting ==> a、b  blocking)。 在被唤醒的那一瞬间,几毫秒中, 生产者A、生产者B是竞争关系, 都在等待消费者C 释放锁,并且争夺对象锁。

5.消费者c重新进入代码块中,因为num=0 ,所以消费者c waiting了 (c running -》c waiting)

6、消费者C 释放锁,生产者B 抢到 对象锁 。。(b blocking -> b running, a blocking 保持不变,) 仔细看 生产者的同步块,

public synchronized void increment() throws InterruptedException {
while(number != 0) {
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我+1完毕了
this.notifyAll();
}

使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。

打印:生产者B=>1,font> , 同时 生产者B也唤醒了消费者c ,消费者C从waiting变成blocing

c waiting -> c blocking

  1. 生产者B重新 进入同步块中,waiting在num!=0 上 ,生产者B释放锁

(b running-> b  waiting)

8.生产者A抢到 对象锁 。。(a blocking -> a running) 仔细看 生产者的同步块, 因为是while判断,生产者A 判断是否要睡觉,并且睡觉

生产者A和生产者B同样等在了num != 0 ,生产者A的状态变为waiting(a running -> a waiting)

此时 a、b 都为waiting 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

只有消费者c 是blocing 可以在cpu上面执行。。。。

这边的重点是,c 唤醒 a、b 【a、b waiting ==> a、b blocking ]

a和b 就算都是blocking,但是也只有一个能进到临界区对num进行操作,

比如a进入临界区之后a waiting了,b进入临界区后,判断睡觉条件,并且睡觉。。。b 也waiting了,

posted @ 2021-09-23 22:39  笨拙的小菜鸟  阅读(200)  评论(0编辑  收藏  举报