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();
}
}
- 測試結果