2 多线程生产消费问题

1 生产者和消费者模式概述

  • 生产消费者问题,实际上主要是包含了两类线程:

    一类是生产者线程用于生产数据
    一类是消费者线程用于消费数据

  • 为了耦合生产者和消费者的关系,通常会采用共享的数据区域,就像一个仓库

    生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
    消费者只需要从共享数据区中获取数据,并不需要关心生产者的行为

在这里插入图片描述

2 虚假唤醒问题

背景
一个卖面的面馆,有一个做面的厨师和一个吃面的食客,需要保证,厨师做一碗面,食客吃一碗面,不能一次性多做几碗面,更不能没有面的时候吃面;按照上述操作,进行三轮做面吃面的操作
代码

public class ThreadDemo4 {
    public static void main(String[] args) throws Exception {
        Noodles noodles = new Noodles();
        new Thread(() -> {
            for (int i = 1; i < 3; i++) noodles.makeNoodles();
        }, "厨师A").start();

        new Thread(() -> {
            for (int i = 1; i < 3; i++) noodles.makeNoodles();
        }, "厨师B").start();

        new Thread(() -> {
            for (int i = 1; i < 3; i++) noodles.eatNoodles();
        }, "食客甲").start();

        new Thread(() -> {
            for (int i = 1; i < 3; i++) noodles.eatNoodles();
        }, "食客已").start();
    }
}

@Slf4j
class Noodles {
    //面的数量
    private int num = 0;

    //做面方法
    public synchronized void makeNoodles() {
        //如果面的数量不为0,则等待食客吃完面再做面
        if (num != 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num++;
        log.info(Thread.currentThread().getName() + "做好了一份面,当前有" + num + "份面");
        //面做好后,唤醒食客来吃
        this.notifyAll();
    }

    //吃面方法
    public synchronized void eatNoodles() {
        //如果面的数量为0,则等待厨师做完面再吃面
        if (num == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        num--;
        log.info(Thread.currentThread().getName() + "吃了一份面,当前有" + num + "份面");
        //吃完则唤醒厨师来做面
        this.notifyAll();
    }
}
// 结果
22:37:06 [INFO ] [厨师A] c.i.juc.Noodles - 厨师A做好了一份面,当前有1份面
22:37:06 [INFO ] [食客已] c.i.juc.Noodles - 食客已吃了一份面,当前有0份面
22:37:06 [INFO ] [厨师B] c.i.juc.Noodles - 厨师B做好了一份面,当前有1份面
22:37:06 [INFO ] [食客甲] c.i.juc.Noodles - 食客甲吃了一份面,当前有0份面
22:37:06 [INFO ] [食客已] c.i.juc.Noodles - 食客已吃了一份面,当前有-1份面
22:37:06 [INFO ] [厨师A] c.i.juc.Noodles - 厨师A做好了一份面,当前有0份面
22:37:06 [INFO ] [食客甲] c.i.juc.Noodles - 食客甲吃了一份面,当前有-1份面
22:37:06 [INFO ] [厨师B] c.i.juc.Noodles - 厨师B做好了一份面,当前有0份面

原因分析

  • 1 初始状态
    在这里插入图片描述
  • 2 厨师A得到操作权,发现面的数量为0,可以做面,面的份数+1,然后唤醒所有线程;
    在这里插入图片描述
  • 3 厨师B得到操作权,发现面的数量为1,不可以做面,执行wait操作;
    在这里插入图片描述
  • 4 厨师A得到操作权,发现面的数量为1,不可以做面,执行wait操作;
    在这里插入图片描述
  • 5 食客甲得到操作权,发现面的数量为1,可以吃面,吃完面后面的数量-1,并唤醒所有线程;
    在这里插入图片描述
  • 6 此时厨师A得到操作权了,因为是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,所以直接面的数量+1,并唤醒其他线程
    在这里插入图片描述
  • 7 此时厨师B得到操作权了,因为是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,所以直接面的数量+1,并唤醒其他线程
    在这里插入图片描述

解决办法

  • 出现虚假唤醒的原因是从阻塞态到就绪态再到运行态没有进行判断,我们只需要让其每次得到操作权时都进行判断就可以了
if(num != 0){
	this.wait();
}
改为	
while(num != 0){
	this.wait();
}

if(num == 0){
	this.wait();
}
改为
while(num == 0){
	this.wait();
}

3 使用Sychronized实现生产者和消费者

  • 为了体现生产和消费过程总的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法就在Object类中Object类的等待和唤醒方法(隐式锁)

    viod wait( ):导致当前线程等待,直到另一个线程调用该对象的notify()方法和notifyAll()方法
    void notify( ):唤醒正在等待对象监视器的单个线程
    void notifyAll( ):唤醒正在等待对象监视器的所有线程
    (注意:wait、notify、notifyAll方法必须要在同步块或同步方法里且成对出现使用)

代码

/*
1.题目:
    现在两个线程,可以操作初始值为0的一个变量,实现一个线程对该变量加1,
    一个线程对该变量减1,交替执行,来10轮,变量的初始值为0
* */
@Slf4j
public class ThreadDemo1 {
    public static void main(String[] args) throws Exception {
        AirCondition airCondition = new AirCondition();
        new Thread(() -> {
            for (int i = 1; i < 4; i++) airCondition.increment();
        }, "线程A").start();
        new Thread(() -> {
            for (int i = 1; i < 4; i++) airCondition.decrement();
        }, "线程B").start();
        new Thread(() -> {
            for (int i = 1; i < 4; i++) airCondition.increment();
        }, "线程C").start();
        new Thread(() -> {
            for (int i = 1; i < 4; i++) airCondition.decrement();
        }, "线程D").start();
    }
}

@Slf4j
class AirCondition {
    private int number = 0;

    public synchronized void increment() {
        //1.判断
        while (number != 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //2.干活
        number++;
        log.info(Thread.currentThread().getName() + ":" + number);
        //3.唤醒
        this.notifyAll();
    }

    public synchronized void decrement() {
        while (number == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number--;
        log.info(Thread.currentThread().getName() + ":" + number);
        this.notifyAll();
    }
}
// 结果
19:50:43 [INFO ] [线程A] c.i.j.AirCondition - 线程A:1
19:50:43 [INFO ] [线程D] c.i.j.AirCondition - 线程D:0
19:50:43 [INFO ] [线程C] c.i.j.AirCondition - 线程C:1
19:50:43 [INFO ] [线程B] c.i.j.AirCondition - 线程B:0
19:50:43 [INFO ] [线程C] c.i.j.AirCondition - 线程C:1
19:50:43 [INFO ] [线程D] c.i.j.AirCondition - 线程D:0
19:50:43 [INFO ] [线程A] c.i.j.AirCondition - 线程A:1
19:50:43 [INFO ] [线程D] c.i.j.AirCondition - 线程D:0
19:50:43 [INFO ] [线程C] c.i.j.AirCondition - 线程C:1
19:50:43 [INFO ] [线程B] c.i.j.AirCondition - 线程B:0
19:50:43 [INFO ] [线程A] c.i.j.AirCondition - 线程A:1
19:50:43 [INFO ] [线程B] c.i.j.AirCondition - 线程B:0

4 使用ReentrantLock实现

ReentrantLock​( ):创建一个ReentrantLock的实例
void lock( ):获得锁
void unlock( ):释放锁

public class ThreadDemo2 {
    public static void main(String[] args) {
        AirCondition2 airCondition = new AirCondition2();

        new Thread(() -> {
            for (int i = 0; i < 4; i++) airCondition.decrement();
        }, "线程A").start();
        new Thread(() -> {
            for (int i = 0; i < 4; i++) airCondition.increment();
        }, "线程B").start();
        new Thread(() -> {
            for (int i = 0; i < 4; i++) airCondition.decrement();
        }, "线程C").start();
        new Thread(() -> {
            for (int i = 0; i < 4; i++) airCondition.increment();
        }, "线程D").start();
    }
}
@Slf4j
class AirCondition2 {
    private int number = 0;
    //定义Lock锁对象
    final Lock lock = new ReentrantLock();
    final Condition condition = lock.newCondition();

    //生产者,如果number=0就 number++
    public void increment() {
        lock.lock();
        try {
            //1.判断
            while (number != 0) {
                try {
                    condition.await();//this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //2.干活
            number++;
            log.info(Thread.currentThread().getName() + ":\t" + number);
            //3.唤醒
            condition.signalAll();//this.notifyAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //消费者,如果number=1,就 number--
    public void decrement() {
        lock.lock();
        try {
            //1.判断
            while (number == 0) {
                try {
                    condition.await();//this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //2.干活
            number--;
            log.info(Thread.currentThread().getName() + ":\t" + number);
            //3.唤醒
            condition.signalAll();//this.notifyAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
// 结果
20:13:39 [INFO ] [线程B] c.i.j.AirCondition2 - 线程B:	1
20:13:39 [INFO ] [线程C] c.i.j.AirCondition2 - 线程C:	0
20:13:39 [INFO ] [线程D] c.i.j.AirCondition2 - 线程D:	1
20:13:39 [INFO ] [线程A] c.i.j.AirCondition2 - 线程A:	0
20:13:39 [INFO ] [线程B] c.i.j.AirCondition2 - 线程B:	1
20:13:39 [INFO ] [线程C] c.i.j.AirCondition2 - 线程C:	0
20:13:39 [INFO ] [线程D] c.i.j.AirCondition2 - 线程D:	1
20:13:39 [INFO ] [线程A] c.i.j.AirCondition2 - 线程A:	0
20:13:39 [INFO ] [线程B] c.i.j.AirCondition2 - 线程B:	1
20:13:39 [INFO ] [线程C] c.i.j.AirCondition2 - 线程C:	0
20:13:39 [INFO ] [线程D] c.i.j.AirCondition2 - 线程D:	1
20:13:39 [INFO ] [线程A] c.i.j.AirCondition2 - 线程A:	0
20:13:39 [INFO ] [线程B] c.i.j.AirCondition2 - 线程B:	1
20:13:39 [INFO ] [线程C] c.i.j.AirCondition2 - 线程C:	0
20:13:39 [INFO ] [线程D] c.i.j.AirCondition2 - 线程D:	1
20:13:39 [INFO ] [线程A] c.i.j.AirCondition2 - 线程A:	0

5 使用ReentrantLock实现 精确通知

代码

/*
    多个线程之间按顺序调用,实现A->B->C  三个线程启动,要求如下:
    AA打印1次,BB打印2次,CC打印3次
    AA打印1次,BB打印2次,CC打印3次
    AA打印1次,BB打印2次,CC打印3次
    ....来3轮
* */
public class ThreadDemo3 {
    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(() -> {
            for (int i = 1; i <= 3; i++) shareResource.print1();
        }, "线程A").start();
        new Thread(() -> {
            for (int i = 1; i <= 3; i++) shareResource.print2();
        }, "线程B").start();
        new Thread(() -> {
            for (int i = 1; i <= 3; i++) shareResource.print3();
        }, "线程C").start();
    }
}

@Slf4j
class ShareResource {
    //设置一个标识,如果是number=1,线程A执行...
    private int number = 1;

    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();

    public void print1() {
        lock.lock();
        try {
            //1.判断
            while (number != 1) {
                condition1.await();
            }
            //2.干活
            for (int i = 1; i <= 1; i++) {
                log.info(Thread.currentThread().getName() + ":\t" + i);
            }
            //3.唤醒
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print2() {
        lock.lock();
        try {
            //1.判断
            while (number != 2) {
                condition2.await();
            }
            //2.干活
            for (int i = 1; i <= 2; i++) {
                log.info(Thread.currentThread().getName() + ":\t" + i);
            }
            //3.唤醒
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print3() {
        lock.lock();
        try {
            //1.判断
            while (number != 3) {
                condition3.await();
            }
            //2.干活
            for (int i = 1; i <= 3; i++) {
                log.info(Thread.currentThread().getName() + ":\t" + i);
            }
            //3.唤醒
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
// 结果
20:19:40 [INFO ] [线程A] c.i.j.ShareResource - 线程A:	1
20:19:40 [INFO ] [线程B] c.i.j.ShareResource - 线程B:	1
20:19:40 [INFO ] [线程B] c.i.j.ShareResource - 线程B:	2
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C:	1
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C:	2
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C:	3
20:19:40 [INFO ] [线程A] c.i.j.ShareResource - 线程A:	1
20:19:40 [INFO ] [线程B] c.i.j.ShareResource - 线程B:	1
20:19:40 [INFO ] [线程B] c.i.j.ShareResource - 线程B:	2
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C:	1
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C:	2
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C:	3
20:19:40 [INFO ] [线程A] c.i.j.ShareResource - 线程A:	1
20:19:40 [INFO ] [线程B] c.i.j.ShareResource - 线程B:	1
20:19:40 [INFO ] [线程B] c.i.j.ShareResource - 线程B:	2
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C:	1
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C:	2
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C:	3
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值