【并发编程】 --- Lock/Condition完成生产者消费者模式+ABCABC顺序打印问题

源码地址:https://github.com/nieandsun/concurrent-study.git



1 生产者消费者问题

使用一个Condition极其类似于wait、notifyAll的使用方法

题目 + 题目分析可参考我之前写的一篇文章《【并发编程】 — 线程间的通信wait、notify、notifyAll》。这里直接上代码了:

  • 生产者和消费者代码
package com.nrsc.ch3.juc_study.communication.pc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class BreadPC {
    /***面包集合*/
    private int number = 0;
    private Lock lock = new ReentrantLock();  //生产者和消费者应该共用一把锁
    private Condition condition = lock.newCondition();//阻塞线程的条件

    /***生产者*/
    public synchronized void produceBread() {
        lock.lock();
        try {
            while (number >= 20) { //注意:多线程的判断不能用if---》有可能出现虚假唤醒问题
                //如果大于等于20箱,就等待  --- 如果这里为大于20的话,则20不会进入while,则会生产出21箱,所以这里应为>=
                condition.await();
            }
            //如果不到20箱就继续生产
            number++; //生产一箱
            log.warn("{}生产一箱面包,现有面包{}个", Thread.currentThread().getName(), number);
            //生产完,唤醒其他线程---> 这里被唤醒的线程可以是消费线程,因为我刚生产完,也可以是其他生产线程
            //但是此时还没有释放锁,要等当前线程把锁释放了,其他线程才能去抢锁
            condition.signalAll();
        } catch (Exception e) {
            log.error("生产者{},等待出错", Thread.currentThread().getName(), e);
        } finally {
            lock.unlock();
        }
    }


    /*** 消费者*/
    public void consumeBread() {

        lock.lock();
        try {
            //如果没有了就等待
            while (number <= 0) { //注意:多线程的判断不能用if---》有可能出现虚假唤醒问题
                condition.await();
            }
            //能走到这里说明i>0,所以进行消费
            number--; //消费一箱
            log.info("{}消费一个面包,现有面包{}个", Thread.currentThread().getName(), number);
            //消费完,通知其他线程
            condition.signalAll();
        } catch (Exception e) {
            log.error("消费者{},等待出错", Thread.currentThread().getName(), e);
        } finally {
            lock.unlock();
        }

    }
}

  • 测试类
package com.nrsc.ch3.juc_study.communication.pc;

/***
 * @author : Sun Chuan
 * @date : 2020/3/19 10:36
 * Description: 
 */
public class MultiTest {


    public static void main(String[] args) throws InterruptedException {

        BreadPC pc = new BreadPC();

        /***
         * 不睡眠几秒,效果不是很好,
         * 因此我在
         *  生产者线程里睡了9秒 --- 因为我觉得生产面包的时间应该长 ☻☻☻
         *  消费者线程里睡了6秒  --- 因为我觉得买面包的时间应该快  ☻☻☻
         */
        //生产者线程
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                //每个线程都不停的生产
                while (true) {
                    try {
                        Thread.sleep(9);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    pc.produceBread();
                }
            }, "生产者" + i).start();
        }


        //消费者线程
        for (int i = 0; i < 4; i++) {
            new Thread(() -> {
                //每个线程都不停的消费
                while (true) {
                    try {
                        Thread.sleep(6);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    pc.consumeBread();
                }
            }, "消费者" + i).start();
        }
    }

}
  • 测试结果

在这里插入图片描述


2 ABCABC。。。三个线程顺序打印问题

多个Condition —》完成定点通知
题目 + 题目分析可参考我之前写的一篇文章《【并发编程】 — 线程间的通信wait、notify、notifyAll》。


2.1 基本不费脑子的实现方式 — 且比较容易感受到定点通知的含义

  • 代码
package com.nrsc.ch3.juc_study.communication.ABCABC;

import lombok.extern.slf4j.Slf4j;

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

/***
 * @author : Sun Chuan
 * @date : 2020/3/20 17:20
 * Description: 
 */
@Slf4j
public class ABCABC {

    static class PrintClass {
        //共用一把锁
        private Lock lock = new ReentrantLock();
        //三个不同的条件
        private Condition c1 = lock.newCondition();
        private Condition c2 = lock.newCondition();
        private Condition c3 = lock.newCondition();

        private int flag = 1; //打印A时使用标志1,B->2, C->3

        public void printA() {
            lock.lock();
            try {
                //如果标识不为1,则利用条件c1将次线程阻塞
                while (flag != 1) {
                    c1.await();
                }
                //走到这里说明标识为1,打印A,并将标识为改为2,且将利用c2条件阻塞的线程唤醒
                flag = 2;
                log.info("A");
                c2.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public void printB() {
            lock.lock();
            try {
                //如果标识不为2,则利用条件c2将次线程阻塞
                while (flag != 2) {
                    c2.await();
                }
                //走到这里说明标识为2,打印B,并将标识为改为3,且将利用c3条件阻塞的线程唤醒
                flag = 3;
                log.info("B");
                c3.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public void printC() {
            lock.lock();
            try {
                //如果标识不为3,则利用条件c3将次线程阻塞
                while (flag != 3) {
                    c3.await();
                }
                //走到这里说明标识为2,打印B,并将标识为改为3,且将利用c3条件阻塞的线程唤醒
                flag = 1;
                log.info("C");
                c1.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }


    public static void main(String[] args) throws InterruptedException {
        PrintClass printClass = new PrintClass();

        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                printClass.printA();
            }
        }, "线程A").start();

        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                printClass.printB();
            }
        }, "线程B").start();

        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                printClass.printC();
            }
        }, "线程C").start();
    }

}

  • 测试结果:

在这里插入图片描述


2.2 比较灵活的方式

有兴趣的可以将这种方式与《【并发编程】 — 线程间的通信wait、notify、notifyAll》那篇文章的实现方式做一下对比,虽然思想极其类似,但自我感觉这种方式要比那篇文章的方式好理解一些。

  • code
package com.nrsc.ch3.juc_study.communication.ABCABC;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class ABCABC2 {

    static class PrintClass implements Runnable {
        //共用一把锁
        private Lock lock;
        //阻塞当前线程的条件 + 阻塞下一个线程的条件
        private Condition current;
        private Condition next;
        //打印的字母
        private String printWord;

        //为了在控制台好看到效果,我这里打印5轮
        private int count = 5;

        public PrintClass(Lock lock, Condition current, Condition next, String printWord) {
            this.lock = lock;
            this.current = current;
            this.next = next;
            this.printWord = printWord;
        }

        @Override
        public void run() {
            lock.lock();
            try {
                while (count > 0) {
                    log.info(printWord);
                    next.signal();  //唤醒利用下一个条件阻塞的线程
                    count--;
                    //这里不加判断会导致程序停不下来,上篇文章分析过具体原因
                    if (count > 0) {
                        current.await(); //利用当前条件将当前线程阻塞
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Condition a = lock.newCondition();
        Condition b = lock.newCondition();
        Condition c = lock.newCondition();

        new Thread(new PrintClass(lock, a, b, "A"), "线程A").start();
        TimeUnit.SECONDS.sleep(1); //确保线程A最先执行

        new Thread(new PrintClass(lock, b, c, "B"), "线程B").start();
        TimeUnit.SECONDS.sleep(1); //确保线程B第2个执行

        new Thread(new PrintClass(lock, c, a, "C"), "线程C").start();
    }

}
  • 测试结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nrsc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值