您的当前位置:首页正文

多线程——虚假唤醒

2024-11-28 来源:个人技术集锦


一、虚假唤醒示例

public class TestProducerAndCustomer {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer producer = new Producer(clerk);
        Consumer consumer = new Consumer(clerk);
        Producer producer2 = new Producer(clerk);
        Consumer consumer2 = new Consumer(clerk);

        new Thread(producer, "生产者A").start();
        new Thread(consumer, "消费者B").start();
        new Thread(producer2, "生产者C").start();
        new Thread(consumer2, "消费者D").start();
    }
}

// 店员类:负责进货和售货
class Clerk {
    private int num = 0; //店里当前的货物量

    public synchronized void get() { //店员进货  每次进货一个(生产者)
        if (num >= 1) {
            System.out.println(Thread.currentThread().getName() + " 库存已满,无法进货");
            try {
                this.wait();
                System.out.println(Thread.currentThread().getName() + " wait后剩余步骤");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " : " + (++num));
        this.notifyAll();
    }

    public synchronized void sale() { //店员卖货 每次卖掉一个货(消费者)
        if (num <= 0) {
            System.out.println(Thread.currentThread().getName() + " 库存已空,无法卖货");
            try {
                this.wait();
                System.out.println(Thread.currentThread().getName() + " wait后剩余步骤");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " : " + (--num));
        this.notifyAll();
    }
}

// 生产者 可以有很多生产者卖货给这个店员
class Producer implements Runnable {
    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.get();
        }
    }
}

//消费者:可以很多消费者找店员买货
class Consumer implements Runnable {
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.sale();
        }
    }
}

运行结果如下:

TIME发生操作仓库数量
TIME1生产者A拿到锁,进行生产1
TIME2生产者A再次拿到锁,因为仓库已满,A等待1
TIME3生产者C拿到锁,仓库已满,C也进入等待1
TIME4消费者D拿到锁,进行消费,同时唤醒等待中的队列(即下一轮锁资源,将由A、B、C、D一起抢占)0
TIME5消费者D拿到锁,仓库为空,D等待;A、B、C抢占锁资源0
TIME6消费者B拿到锁,仓库为空,B等待0
TIME7生产者C拿到锁,因为C是从等待中被唤醒的,所以从wait之后开始执行,进行生产 ,并唤醒B、D1
TIME8生产者C再次拿到锁,仓库已满,C进入等待1
TIME9生产者A拿到锁,因为A是从等待中被唤醒的,所以从wait之后执行,进行生产,并唤醒C2(发生虚假唤醒)

  为什么会产生这种原因呢?因为我们在进行是否wait是用的是if循环,if语句只执行一次,当等待中的锁被唤醒,它将直接继续向下执行,如果使用while,就会循环判断是否满足条件,如果不满足将继续进入等待。

二、正确打开方式

代码如下:

public class TestProducerAndCustomer {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer producer = new Producer(clerk);
        Consumer consumer = new Consumer(clerk);
        Producer producer2 = new Producer(clerk);
        Consumer consumer2 = new Consumer(clerk);

        new Thread(producer, "生产者A").start();
        new Thread(consumer, "消费者B").start();
        new Thread(producer2, "生产者C").start();
        new Thread(consumer2, "消费者D").start();
    }
}

// 店员类:负责进货和售货
class Clerk {
    private int num = 0; //店里当前的货物量

    public synchronized void get() { //店员进货  每次进货一个(生产者)
        while (num >= 1) {
            System.out.println(Thread.currentThread().getName() + " 库存已满,无法进货");
            try {
                this.wait();
                System.out.println(Thread.currentThread().getName() + " wait后剩余步骤");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " : " + (++num));
        this.notifyAll();
    }

    public synchronized void sale() { //店员卖货 每次卖掉一个货(消费者)
        while (num <= 0) {
            System.out.println(Thread.currentThread().getName() + " 库存已空,无法卖货");
            try {
                this.wait();
                System.out.println(Thread.currentThread().getName() + " wait后剩余步骤");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " : " + (--num));
        this.notifyAll();
    }
}

// 生产者 可以有很多生产者卖货给这个店员
class Producer implements Runnable {
    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.get();
        }
    }
}

//消费者:可以很多消费者找店员买货
class Consumer implements Runnable {
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.sale();
        }
    }
}

总结

在进行wait操作的条件判断时,注意判断语句的选择,以防出现虚假唤醒。
Good Good Study,Day Day Up!

显示全文