Java并發(fā)編程之線程間的通信
一、概念簡(jiǎn)介
1、線程通信
在操作系統(tǒng)中,線程是個(gè)獨(dú)立的個(gè)體,但是在線程執(zhí)行過(guò)程中,如果處理同一個(gè)業(yè)務(wù)邏輯,可能會(huì)產(chǎn)生資源爭(zhēng)搶,導(dǎo)致并發(fā)問(wèn)題,通常使用互斥鎖來(lái)控制該邏輯。但是在還有這樣一類場(chǎng)景,任務(wù)執(zhí)行是有順序控制的,例如常見的報(bào)表數(shù)據(jù)生成:
- 啟動(dòng)數(shù)據(jù)分析任務(wù),生成報(bào)表數(shù)據(jù);
- 報(bào)表數(shù)據(jù)存入指定位置數(shù)據(jù)容器;
- 通知數(shù)據(jù)搬運(yùn)任務(wù),把數(shù)據(jù)寫入報(bào)表庫(kù);
該場(chǎng)景在相對(duì)復(fù)雜的系統(tǒng)中非常常見,如果基于多線程來(lái)描述該過(guò)程,則需要線程之間通信協(xié)作,才能有條不紊的處理該場(chǎng)景業(yè)務(wù)。
2、等待通知機(jī)制
如上的業(yè)務(wù)場(chǎng)景,如果線程A生成數(shù)據(jù)過(guò)程中,線程B一直在訪問(wèn)數(shù)據(jù)容器,判斷該過(guò)程的數(shù)據(jù)是否已經(jīng)生成,則會(huì)造成資源浪費(fèi)。正常的流程應(yīng)該如圖,線程A和線程B同時(shí)啟動(dòng),線程A開始處理數(shù)據(jù)生成任務(wù),線程B嘗試獲取容器數(shù)據(jù),數(shù)據(jù)還沒(méi)過(guò)來(lái),線程B則進(jìn)入等待狀態(tài),當(dāng)線程A的任務(wù)處理完成,則通知線程B去容器中獲取數(shù)據(jù),這樣基于線程等待和通知的機(jī)制來(lái)協(xié)作完成任務(wù)。
3、基礎(chǔ)方法
等待/通知機(jī)制的相關(guān)方法是Java中Object層級(jí)的基礎(chǔ)方法,任何對(duì)象都有該方法:
- notify:隨機(jī)通知一個(gè)在該對(duì)象上等待的線程,使其結(jié)束wait狀態(tài)返回;
- notifyAll:?jiǎn)拘言谠搶?duì)象上所有等待的線程,進(jìn)入對(duì)象鎖爭(zhēng)搶隊(duì)列中;
- wait:線程進(jìn)入waiting等待狀態(tài),不會(huì)爭(zhēng)搶鎖對(duì)象,也可以設(shè)置等待時(shí)間;
線程的等待通知機(jī)制,就是基于這幾個(gè)基礎(chǔ)方法。
二、等待通知原理
1、基本原理
等待/通知機(jī)制,該模式下指線程A在不滿足任務(wù)執(zhí)行的情況下調(diào)用對(duì)象wait()方法進(jìn)入等待狀態(tài),線程B修改了線程A的執(zhí)行條件,并調(diào)用對(duì)象notify()或者notifyAll()方法,線程A收到通知后從wait狀態(tài)返回,進(jìn)而執(zhí)行后續(xù)操作。兩個(gè)線程通過(guò)基于對(duì)象提供的wait()/notify()/notifyAll()等方法完成等待和通知間交互,提高程序的可伸縮性。
2、實(shí)現(xiàn)案例
通過(guò)線程通信解決上述數(shù)據(jù)生成和存儲(chǔ)任務(wù)的解耦流程。
public class NotifyThread01 { static Object lock = new Object() ; static volatile List<String> dataList = new ArrayList<>(); public static void main(String[] args) throws Exception { Thread saveThread = new Thread(new SaveData(),"SaveData"); saveThread.start(); TimeUnit.SECONDS.sleep(3); Thread dataThread = new Thread(new AnalyData(),"AnalyData"); dataThread.start(); } // 等待數(shù)據(jù)生成,保存 static class SaveData implements Runnable { @Override public void run() { synchronized (lock){ while (dataList.size()==0){ try { System.out.println(Thread.currentThread().getName()+"等待..."); lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("SaveData .."+ dataList.get(0)+dataList.get(1)); } } } // 生成數(shù)據(jù),通知保存 static class AnalyData implements Runnable { @Override public void run() { synchronized (lock){ dataList.add("hello,"); dataList.add("java"); lock.notify(); System.out.println("AnalyData End..."); } } } }
注意:除了dataList滿足寫條件,還要在AnalyData線程執(zhí)行通知操作。
三、管道流通信
1、管道流簡(jiǎn)介
基本概念
管道流主要用于在不同線程間直接傳送數(shù)據(jù),一個(gè)線程發(fā)送數(shù)據(jù)到輸出管道,另一個(gè)線程從輸入管道中讀取數(shù)據(jù),進(jìn)而實(shí)現(xiàn)不同線程間的通信。
實(shí)現(xiàn)分類
管道字節(jié)流:PipedInputStream和PipedOutputStream;
管道字符流:PipedWriter和PipedReader;
新IO管道流:Pipe.SinkChannel和Pipe.SourceChannel;
2、使用案例
public class NotifyThread02 { public static void main(String[] args) throws Exception { PipedInputStream pis = new PipedInputStream(); PipedOutputStream pos = new PipedOutputStream(); // 鏈接輸入流和輸出流 pos.connect(pis); // 寫數(shù)據(jù)線程 new Thread(new Runnable() { public void run() { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 將從鍵盤讀取的數(shù)據(jù)寫入管道流 PrintStream ps = new PrintStream(pos); while (true) { try { System.out.print(Thread.currentThread().getName()); ps.println(br.readLine()); Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } } } }, "輸入數(shù)據(jù)線程:").start(); // 讀數(shù)據(jù)線程 new Thread(new Runnable() { public void run() { BufferedReader br = new BufferedReader(new InputStreamReader(pis)); while (true) { try { System.out.println(Thread.currentThread().getName() + br.readLine()); } catch (IOException e) { e.printStackTrace(); } } } }, "輸出數(shù)據(jù)線程:").start(); } }
寫線程向管道流寫入數(shù)據(jù),讀線程讀取數(shù)據(jù),完成基本通信流程。
四、生產(chǎn)消費(fèi)模式
1、業(yè)務(wù)場(chǎng)景
基于線程等待通知機(jī)制:實(shí)現(xiàn)工廠生產(chǎn)一件商品,通知商店賣出一件商品的業(yè)務(wù)流程。
2、代碼實(shí)現(xiàn)
public class NotifyThread03 { public static void main(String[] args) { Product product = new Product(); ProductFactory productFactory = new ProductFactory(product); ProductShop productShop = new ProductShop(product); productFactory.start(); productShop.start(); } } // 產(chǎn)品 class Product { public String name ; public double price ; // 產(chǎn)品是否生產(chǎn)完畢,默認(rèn)沒(méi)有 boolean flag ; } // 產(chǎn)品工廠:生產(chǎn) class ProductFactory extends Thread { Product product ; public ProductFactory (Product product){ this.product = product; } @Override public void run() { int i = 0 ; while (i < 20) { synchronized (product) { if (!product.flag){ if (i%2 == 0){ product.name = "鼠標(biāo)"; product.price = 79.99; } else { product.name = "鍵盤"; product.price = 89.99; } System.out.println("產(chǎn)品:"+product.name+"【價(jià)格:"+product.price+"】出廠..."); product.flag = true ; i++; // 通知消費(fèi)者 product.notifyAll(); } else { try { // 進(jìn)入等待狀態(tài) product.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } // 產(chǎn)品商店:銷售 class ProductShop extends Thread { Product product ; public ProductShop (Product product){ this.product = product ; } @Override public void run() { while (true) { synchronized (product) { if (product.flag == true ){ System.out.println("產(chǎn)品:"+product.name+"【價(jià)格"+(product.price*2)+"】賣出..."); product.flag = false ; product.notifyAll(); //喚醒生產(chǎn)者 } else { try { product.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
流程描述:ProductFactory生成一件商品,通知商店售賣,通過(guò)flag標(biāo)識(shí)判斷控制是否進(jìn)入等待狀態(tài),商店賣出商品后,再次通知工廠生產(chǎn)商品。
五、源代碼地址
GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent
以上就是Java并發(fā)編程之線程間的通信的詳細(xì)內(nèi)容,更多關(guān)于Java 線程間通信的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java一維數(shù)組和二維數(shù)組元素默認(rèn)初始化值的判斷方式
這篇文章主要介紹了Java一維數(shù)組和二維數(shù)組元素默認(rèn)初始化值的判斷方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08詳解Java使用Pipeline對(duì)Redis批量讀寫(hmset&hgetall)
本篇文章主要介紹了Java使用Pipeline對(duì)Redis批量讀寫(hmset&hgetall),具有一定的參考價(jià)值,有興趣的可以了解一下。2016-12-12java判斷某個(gè)點(diǎn)是否在所畫多邊形/圓形內(nèi)
這篇文章主要為大家詳細(xì)介紹了java判斷某個(gè)點(diǎn)是否在所畫多邊形或圓形內(nèi)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05java基礎(chǔ)之String知識(shí)總結(jié)
今天帶大家來(lái)回顧一下Java基礎(chǔ),文中詳細(xì)總結(jié)了String的相關(guān)知識(shí),對(duì)正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有很好的幫助,需要的朋友可以參考下2021-05-05fastjson轉(zhuǎn)換對(duì)象實(shí)體@JsonProperty不生效問(wèn)題及解決
這篇文章主要介紹了fastjson轉(zhuǎn)換對(duì)象實(shí)體@JsonProperty不生效問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08Hikari?數(shù)據(jù)庫(kù)連接池內(nèi)部源碼實(shí)現(xiàn)的小細(xì)節(jié)
這篇文章主要介紹了Hikari?數(shù)據(jù)庫(kù)連接池內(nèi)部源碼實(shí)現(xiàn)的小細(xì)節(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02Java實(shí)現(xiàn)SHA-1算法實(shí)例
這篇文章主要介紹了Java實(shí)現(xiàn)SHA-1算法,實(shí)例分析了java實(shí)現(xiàn)SHA-1算法的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03Java合并兩個(gè)List后并去掉重復(fù)項(xiàng)的兩種做法
工作中很多時(shí)候需要用到合并兩個(gè)List并去除其中的重復(fù)內(nèi)容,這是一個(gè)很簡(jiǎn)單的操作,實(shí)現(xiàn)的方法也多種多樣,這篇文章主要給大家介紹了關(guān)于Java合并兩個(gè)List后并去掉重復(fù)項(xiàng)的兩種做法,需要的朋友可以參考下2023-10-10