Java多线程——生产者消费者示例

1. 等待/通知机制

  等待/通知机制在我们生活中很常见,例如,餐厅服务员和厨师之间,只有厨师做好菜之后,通知服务员,服务员才能上菜;而在未做好菜之前,服务员只能等待厨师做菜。除了这个例子外,等待/通知机制中,最典型的就是生产者和消费者模型,下边我们用代码实现该模型。

2. 单一生产者和消费者

  Java中等待/通知,通常使用Object类中的wait()方法阻塞线程,线程进入等待吃,notify()/notifyAll()方法唤醒线程。其中notify()notifyAll()方法的区别在于,notify()方法,值唤醒等待池线程中的一个线程,而notifyAll()唤醒所有等待池中的线程。下边我们使用代码,实现等待/通知中的典型例子,生产者和消费者模型。
  以生产汽车为例,创建一个汽车工厂类,其中有两个一个生产汽车和一个销售汽车的方法:

@Slf4j
public class CarFactory {

    private int num;

    private Object obj;

    public CarFactory(Object obj) {
        this.obj = obj;
    }

    public void createCar() {
        synchronized (obj) {
            try {
                while (num == 10) {
                    log.info("当前数量={},暂停生产", num);
                    obj.wait();
                }
                num++;
                log.info("生产者:{}, 生产了一辆汽车,当前总量:{}", Thread.currentThread().getName(), num);
                obj.notify();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void consumerCar() {
        synchronized (obj) {
            try {
                while (num == 1) {
                    log.info("当前数量={},暂停销售", num);
                    obj.wait();
                }
                num--;
                log.info("消费者:{}, 购买了了一辆汽车,当前总量:{}", Thread.currentThread().getName(), num);
                obj.notify();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
复制代码

创建两个线程,分别指代生产者和消费者:

@AllArgsConstructor
public class Producer implements Runnable {

    private CarFactory carFactory;

    @Override
    public void run() {
        while (true) {
            carFactory.createCar();
        }
    }
}

@AllArgsConstructor
public class Consumer implements Runnable {

    private CarFactory carFactory;

    @Override
    public void run() {
        while (true) {
            carFactory.consumerCar();
        }
    }
}
复制代码

测试方法:

public static void main(String[] args) throws Exception {
    CarFactory carFactory = new CarFactory(new Object());
    Producer producer = new Producer(carFactory);
    Consumer consumer = new Consumer(carFactory);
    new Thread(producer, "生产者A").start();
    new Thread(consumer, "消费者A").start();
}
复制代码

  上边创建了一个生产者和一个消费者,程序运行的过程大致如下:

  1. 生产者获得锁之后,开始生产产品;
  2. 生产者生产的数量达到10之后,调用wait()方法,阻塞生产者线程,生产者释放锁;
  3. 生产者释放锁之后,消费者获得锁,消费一个后,调用notify()方法,唤醒一个等待池中的线程,而此时等待池中只有一个生产者,因此唤醒生产者,等待消费者释放锁;
  4. 消费者消费的只剩一个之后,调用wait()方法,阻塞消费者,释放锁;
  5. 生产者再次得到锁,然后再次从第1步开始执行。

  需要注意的地方:判断线程是否该阻塞时,需要使用while而不是if;因为,线程唤醒之后,会继续从上一次停止的地方开始执行;如果使用if,下一次唤醒之后,不会再次判断,会继续执行if后边的代码,这里就是会继续生产产品,造成误差,单个生产者消费者时,可能不明显,多个生产者消费者时会更加明显的体现。

以上测试代码,执行结果:

19:24:33.673 [生产者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 生产者:生产者A, 生产了一辆汽车,当前总量:2
19:24:33.673 [生产者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 生产者:生产者A, 生产了一辆汽车,当前总量:3
19:24:33.673 [生产者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 生产者:生产者A, 生产了一辆汽车,当前总量:4
19:24:33.673 [生产者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 生产者:生产者A, 生产了一辆汽车,当前总量:5
19:24:33.673 [生产者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 生产者:生产者A, 生产了一辆汽车,当前总量:6
19:24:33.673 [生产者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 生产者:生产者A, 生产了一辆汽车,当前总量:7
19:24:33.673 [生产者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 生产者:生产者A, 生产了一辆汽车,当前总量:8
19:24:33.673 [生产者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 生产者:生产者A, 生产了一辆汽车,当前总量:9
19:24:33.673 [生产者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 生产者:生产者A, 生产了一辆汽车,当前总量:10
19:24:33.673 [生产者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 当前数量=10,暂停生产
19:24:33.673 [消费者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 消费者:消费者A, 购买了了一辆汽车,当前总量:9
19:24:33.673 [消费者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 消费者:消费者A, 购买了了一辆汽车,当前总量:8
19:24:33.673 [消费者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 消费者:消费者A, 购买了了一辆汽车,当前总量:7
19:24:33.673 [消费者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 消费者:消费者A, 购买了了一辆汽车,当前总量:6
19:24:33.673 [消费者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 消费者:消费者A, 购买了了一辆汽车,当前总量:5
19:24:33.673 [消费者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 消费者:消费者A, 购买了了一辆汽车,当前总量:4
19:24:33.673 [消费者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 消费者:消费者A, 购买了了一辆汽车,当前总量:3
19:24:33.673 [消费者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 消费者:消费者A, 购买了了一辆汽车,当前总量:2
19:24:33.673 [消费者A] INFO com.sachin.threadlearn.waitAndNotify.CarFactory - 消费者:消费者A, 购买了了一辆汽车,当前总量:1
.........
复制代码

3. 多个生产者和消费者

  上边展示了单个生产者消费者的情况,下边看一下多个生产者消费者的情况;之前我们说过notify()方法,只会唤醒等待池中的一个线程,在多个生产者消费者的情况下,如果继续使用notify()方法,每次就只能唤醒一个线程;
  更加严重的情况可能会出现“假死”,即,消费者唤醒了一个生产者,然后释放锁,生产者A开始生产,同时,调用notify()方法唤醒线程,如果唤醒了另一个生产者B;那么,当达到阻塞条件,生产者A进入WAITING状态,生产者B得到锁,开始执行同步块,但是刚执行就发现达到了阻塞条件也进入WAITING状态;此时,还没来得及唤醒线程;所有的线程都处于WAITING状态,就是线程“假死”。
  因此,多个生产者消费者我们需要使用notifyAll()方法,唤醒所有的线程,其他地方可以不做修改;测试方法,多创建几个生产者和消费者就可以了,这里不再重复代码。
  但是,使用notifyAll()方法,唤醒了所有的等待池中的线程,但是,生产者生产过程中,如果即将达到阻塞状态时,唤醒所有线程,唤醒的生产者还是会再次阻塞,浪费时间;如果我们只唤醒消费者就了,使用LockCondition可以实现我们的需求:

@Slf4j
public class CarFactory {

    private int num;

    private Lock lock = new ReentrantLock();

    private Condition producerCondition = lock.newCondition();

    private Condition consumerCondition = lock.newCondition();

    public void producerCar() {
        try {
            lock.lock();
            while (num == 10) {
                log.info("数量已达上限,停止生产");
                producerCondition.await();
            }
            num++;
            log.info("生产者:{}, 生产了一个汽车,总量为:{}", Thread.currentThread().getName(), num);
            consumerCondition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void consumerCar() {
        try {
            lock.lock();
            while (num == 1) {
                log.info("没车了,停止销售");
                consumerCondition.await();
            }
            num--;
            log.info("消费者:{}, 购买了一个汽车,总量为:{}", Thread.currentThread().getName(), num);
            producerCondition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
复制代码

  上边代码,创建了两个Condition对象,分别用来阻塞和唤醒生产者消费者;生产方法中,使用producerCondition对象,阻塞生产者,但是使用consumerCondition对象的singAll()方法,来唤醒线程,因为消费者使用了也是该对象进行阻塞,因此,此时唤醒的都是消费者;对于消费者也是一样。
测试方法:

public static void main(String[] args) {
    CarFactory carFactory = new CarFactory();
    ExecutorService service = Executors.newCachedThreadPool();
    Producer producer = new Producer(carFactory);
    Consumer consumer = new Consumer(carFactory);
    for (int i = 0; i < 3; i++) {
        service.execute(producer);
        service.execute(consumer);
    }
    service.shutdown();
}
复制代码

转载于:https://juejin.im/post/5c7ccd50f265da2de52daa14

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值