【併發編程】 --- 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();
    }

}
  • 測試結果

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章