线程通信之生产者消费者

689 阅读1分钟

这是我参与8月更文挑战的第23天,活动详情查看:8月更文挑战

生产者消费者问题

生产者消费者问题是线程通信的经典问题,两个角色生产者消费者,他们共用一块资源空间,当空间空的时候消费者阻塞,当空间满的时候生产者阻塞。

image.png

例子

一个初始值为0的变量,两个线程对它进行操作,一个加1一个减1交替进行

package com.lemon;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class lesson8_Consumer {
    public static void main(String[] args) {
        ResourceInfo resourceInfo = new ResourceInfo();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    resourceInfo.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AAA").start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    resourceInfo.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }, "BBB").start();
    }
}

class ResourceInfo {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private int num;

    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while (num != 0) {
                condition.await(); //等待不能生产
            }
            num++;
            System.out.println(Thread.currentThread().getName() + "\t num:" + num);
            condition.signalAll(); //通知唤醒
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (num == 0) {
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "\t num:" + num);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }


    }
}

为什么判断num的值的时候用while而不是if?

虚假唤醒

当一个条件满足的时候,很多线程都被唤醒了,但是有些是无用的唤醒。如果说原来num为0,生产之后num为1,这样就会唤醒其他消费者来消费,但是实际上这个num=1只会被一个消费者消费。

while不会出现虚假唤醒: 线程被唤醒后会重新判断while中的条件,通过了while循环才会执行后面的代码

if会出现虚假唤醒: 唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码

在jdk1.8文档中 Object - > wait ->可以看到这段话:

线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:

            synchronized (obj) {
                     while (<condition does not hold>)
                         obj.wait(timeout);
                     ... // Perform action appropriate to condition
                 }