線程間通信與協作方式之——wait-notify機制

来源:https://www.cnblogs.com/smartchen/archive/2018/07/08/9280877.html
-Advertisement-
Play Games

大家好,上篇文章為大家介紹了線程間通信和協作的一些基本方式,那這篇文章就來介紹一下經典的wait-notify機制吧。 什麼是wait-notify機制? 想象一下有兩個線程A、B,如果業務場景中需要這兩個線程交替執行任務(比如A執行完一次任務後換B執行,B執行完後再換A執行這樣重覆交替),之前的基 ...


大家好,上篇文章為大家介紹了線程間通信和協作的一些基本方式,那這篇文章就來介紹一下經典的wait-notify機制吧。

 

什麼是wait-notify機制?

想象一下有兩個線程A、B,如果業務場景中需要這兩個線程交替執行任務(比如A執行完一次任務後換B執行,B執行完後再換A執行這樣重覆交替),之前的基本通信方式只能讓線程暫停一段指定時間,Join方法也無法做到這種交替執行的要求,那怎麼辦呢?

別急,針對這種場景java同樣為我們提供了一種經典的線程通信方式——wait-notify機制,這裡涉及到下麵的三個方法(關於鎖的知識後文會詳細講):

wait方法:當前線程在調用wait方法會先讓出當前線程持有的對象鎖以便讓其他線程能夠獲取,然後當前線程會停止執行併進入WAITING狀態。直到接收到喚醒或中斷信號後,當前線程才會繼續嘗試獲取對象鎖。如果此時獲取對象鎖成功,就能繼續執行任務。

notify方法:當前線程的任務即將執行完畢併發出喚醒信號,此時只有接收到喚醒信號的線程才會嘗試獲取對象鎖。當然此時可能獲取對象鎖會失敗,因為notify方法不會即時釋放鎖,而是需要等到線程執行完畢後才會真正釋放鎖。

notifyAll方法:和notify方法作用相似,唯一不同的就是該方法會對當前所有在等待這個對象鎖的線程發出喚醒信號。至於最終是哪個線程搶到了對象鎖,就要看哪個線程比較“幸運”啦。

 

關於這幾個方法,還有以下兩點需要關註:

1.wait、notify、notifyAll這三個方法都是Object類中定義的,而Object類是所有類的父類,所以在java中的所有對象都會繼承這三個方法。

2.這三個方法必須在同步塊中被調用(之後會介紹同步塊),如果在同步塊之外調用這三個方法,java會拋出java.lang.IllegalMonitorStateException這個異常。

 

 

基於wait-notify機制的單生產者-單消費者模型

上面已經介紹了wait-notify機制用到的方法以及需要註意的點,實際上針對這個機制,有一個非常著名、非常經典的模型——生產者消費者模型。

什麼是生產者-消費者模型呢?簡單來說就是這麼個場景:有兩種線程分別是生產者線程和消費者線程,還有一個固定大小的資源隊列。

生產者的任務是根據原料生產出產品,並將生產好的產品往隊列里扔;消費者的任務呢就是從隊列裡面拿已經生產好的產品去進行包裝。

我們可以看到在這個場景中,因為隊列可容納的資源是有限的,所以當隊列滿時,生產者就沒辦法繼續往隊列里放產品,此時生產者就需要等待消費者從隊列里拿走產品後,才能繼續往隊列里放產品;

而消費者也是一樣,當隊列為空時,消費者就無法從隊列里拿到產品,此時就需要等待生產者成功生產出產品並往隊列里扔,才能繼續從隊列里拿產品。

這個場景是wait-notify機制最適合發揮作用的場景,下麵是一個單生產者-單消費者的模擬代碼:

  1 /**
  2  * 基於wait-notify機制的單生產者-消費者模型
  3  */
  4 public class ProducerAndConsumer {
  5 
  6     public static void main(String[] args) {
  7         Resource resource = new Resource();
  8         //生產者線程
  9         ProducerThread p1 = new ProducerThread(resource);
 10         //消費者線程
 11         ConsumerThread c1 = new ConsumerThread(resource);
 12 
 13         p1.start();
 14         c1.start();
 15 
 16     }
 17 }
 18 
 19 
 20 /**
 21  * 公共資源類
 22  * @author
 23  *
 24  */
 25 class Resource{//重要
 26     //當前資源數量
 27     private int num = 0;
 28     //資源池中允許存放的資源數目
 29     private int size = 10;
 30 
 31     /**
 32      * 從資源池中取走資源
 33      */
 34     public synchronized void remove(){
 35         if(num > 0){
 36             num--;
 37             System.out.println("消費者" + Thread.currentThread().getName() +
 38                     "消耗一件資源," + "當前線程池有" + num + "個");
 39             notifyAll();//通知生產者生產資源
 40         }else{
 41             try {
 42                 //如果沒有資源,則消費者進入等待狀態
 43                 wait();
 44                 System.out.println("消費者" + Thread.currentThread().getName() + "線程進入等待狀態");
 45             } catch (InterruptedException e) {
 46                 e.printStackTrace();
 47             }
 48         }
 49     }
 50     /**
 51      * 向資源池中添加資源
 52      */
 53     public synchronized void add(){
 54         if(num < size){
 55             num++;
 56             System.out.println("生產者" + Thread.currentThread().getName() + "生產一件資源,當前資源池有"
 57                     + num + "個");
 58             //通知等待的消費者
 59             notifyAll();
 60         }else{
 61             //如果當前資源池中有10件資源
 62             try{
 63                 wait();//生產者進入等待狀態,並釋放鎖
 64                 System.out.println(Thread.currentThread().getName()+"線程進入等待");
 65             }catch(InterruptedException e){
 66                 e.printStackTrace();
 67             }
 68         }
 69     }
 70 }
 71 
 72 
 73 /**
 74  * 消費者線程
 75  */
 76 class ConsumerThread extends Thread{
 77     private Resource resource;
 78     public ConsumerThread(Resource resource){
 79         this.resource = resource;
 80     }
 81     @Override
 82     public void run() {
 83         while(true){
 84             try {
 85                 Thread.sleep(1000);
 86             } catch (InterruptedException e) {
 87                 e.printStackTrace();
 88             }
 89             resource.remove();
 90         }
 91     }
 92 }
 93 
 94 
 95 /**
 96  * 生產者線程
 97  */
 98 class ProducerThread extends Thread{
 99     private Resource resource;
100     public ProducerThread(Resource resource){
101         this.resource = resource;
102     }
103     @Override
104     public void run() {
105         //不斷地生產資源
106         while(true){
107             try {
108                 Thread.sleep(1000);
109             } catch (InterruptedException e) {
110                 e.printStackTrace();
111             }
112             resource.add();
113         }
114     }
115 
116 }
View Code

童鞋們可以運行代碼試試,這裡資源池最大允許放10個產品。

這裡留一個問題給大家思考,如果我這裡的add和remove方法不加synchronized修飾,就會拋出java.lang.IllegalMonitorStateException異常,那麼是什麼原因導致java必須要這麼做呢?我會在介紹synchronized關鍵字的時候公佈答案。

 

好了,wait-notify機制到這裡就介紹完畢,希望大家能夠理解。下篇文章會為大家講解一下volatile這個關鍵字的用法。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 命令: hset info namehgetall infohkeys infohvlls info m系列批量處理: hmset info2 k1 v1 k2 v2 hmget info2 k1 k2 hlen獲取有幾個key hlen info2 hexists判斷是否存在: hexists i ...
  • 本文關鍵詞: java集合框架 框架設計理念 容器 繼承層級結構 繼承圖 集合框架中的抽象類 主要的實現類 實現類特性 集合框架分類 集合框架併發包 併發實現類 什麼是容器? 由一個或多個確定的元素所構成的整體叫做集合。 容器用來包裝或裝載物品的貯存器 (如箱、罐、壇)或者成形或柔軟不成形的包覆材料 ...
  • Flask的g對象 g可以可以看作是單詞global的縮寫,使用“from flask import g”導入,g對象的作用是保存一些在一次請求中多個地方的都需要用到的數據,這些數據可能在用到的時候都需要去進行判斷或其他處理之後才能獲得,如果在第一次獲取的時候就存放到g對象中,就可以避免一些不必要的 ...
  • 中午吃飯時看了一下陸毅版的《三國》,剛好看的是蜀軍缺糧,諸葛亮讓王平去劫司馬懿的糧。司馬懿看蜀軍用木牛流馬運量很方便,就搶了蜀軍的木牛流馬仿製了一批,結果司馬懿用它運糧時,被王平冒充司馬懿的人在驗糧時,對木牛流馬動了手腳,結果木牛流馬不能動彈了,被蜀軍把幾十萬擔的糧食搶走了。看到這裡的時候,我想到了 ...
  • 下麵是EXE代碼 ...
  • 模型概述 有一DAG,問最少加多少條邊能夠使圖強連通。 題目描述 一些學校連入一個電腦網路。那些學校已訂立了協議:每個學校都會給其它的一些學校分發軟體(稱作“接受學校”)。註意即使 B 在 A 學校的分發列表中, A 也不一定在 B 學校的列表中。 你要寫一個程式計算,根據協議,為了讓網路中所有的學 ...
  • Description 給定一個含有n個數的序列a[1],a[2],a[3]……a[n],程式必須回答這樣的詢問:對於給定的i,j,k,在a[i],a[i+1 ],a[i+2]……a[j]中第k小的數是多少(1≤k≤j-i+1),並且,你可以改變一些a[i]的值,改變後,程式還能針對改 變後的a繼續 ...
  • 前言 微服務概念已經非常流行,這影響了現在架構的思想潮流。 如今,使用spring cloud體系搭建微服務架構的公司越來越多,成本低,出線上產品快,模塊全,開源等原因未來可能更加流行。 一般,我們需要一個監控系統來監控應用的數據,比如記憶體,磁碟,線程情況,資料庫連接池,配置信息,jvm信息等等。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...