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

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


文章目录

  • 1 生产者消费者问题
  • 2 ABCABC。。。三个线程顺序打印问题
    • 2.1 基本不费脑子的实现方式 --- 且比较容易感受到定点通知的含义
    • 2.2 比较灵活的方式


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->3public 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;}@Overridepublic 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();}}
  • 测试结果

在这里插入图片描述


本文链接:https://www.ngui.cc/el/1113654.html
Copyright © 2010-2022 ngui.cc 版权所有 |关于我们| 联系方式| 豫B2-20100000