Java多線程與高并發專題——Condition 和 wait/notify的關系

引入

上一篇關于Condition,我們對Condition有了進一步了解,在之前生產/消費者模式一文,我們講過如何用 Condition 和 wait/notify 來實現生產者/消費者模式,其中的精髓就在于用Condition 和 wait/notify 來實現簡易版阻塞隊列,我們先來分別回顧一下這兩段代碼。

用 Condition 實現簡易版阻塞隊列

代碼如下所示:

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/*** 一個使用 Condition 實現的阻塞隊列類。* 該類提供了一個線程安全的隊列,支持在隊列滿時阻塞插入操作,* 在隊列空時阻塞移除操作。*/
public class MyBlockingQueueForCondition {// 存儲元素的隊列private Queue queue;// 隊列的最大容量private int max = 16;// 用于線程同步的可重入鎖private ReentrantLock lock = new ReentrantLock();// 當隊列不為空時的條件private Condition notEmpty = lock.newCondition();// 當隊列不為滿時的條件private Condition notFull = lock.newCondition();/*** 構造函數,初始化隊列的最大容量。* * @param size 隊列的最大容量*/public MyBlockingQueueForCondition(int size){this.max = size;queue = new LinkedList();}/*** 向隊列中插入一個元素。* 如果隊列已滿,線程將被阻塞,直到隊列有空間。* * @param o 要插入的元素* @throws InterruptedException 如果線程在等待時被中斷*/public void put(Object o) throws InterruptedException {// 獲取鎖lock.lock();try {// 當隊列已滿時,線程等待while (queue.size() == max) {notFull.await();}// 向隊列中添加元素queue.add(o);// 通知所有等待隊列不為空的線程notEmpty.signalAll();} finally {// 釋放鎖lock.unlock();}}/*** 從隊列中移除并返回一個元素。* 如果隊列為空,線程將被阻塞,直到隊列中有元素。* * @return 隊列中的第一個元素* @throws InterruptedException 如果線程在等待時被中斷*/public Object take() throws InterruptedException {// 獲取鎖lock.lock();try {// 當隊列為空時,線程等待while (queue.size() == 0) {notEmpty.await();}// 從隊列中移除并獲取元素Object item = queue.remove();// 通知所有等待隊列不為滿的線程notFull.signalAll();return item;} finally {// 釋放鎖lock.unlock();}}
}

在上面的代碼中,首先定義了一個隊列變量 queue,其最大容量是 16;然后定義了一個ReentrantLock 類型的 Lock 鎖,并在 Lock 鎖的基礎上創建了兩個 Condition,一個是 notEmpty,另一個是 notFull,分別代表隊列沒有空和沒有滿的條件;最后,聲明了 put 和 take 這兩個核心方法。

用 wait/notify 實現簡易版阻塞隊列

我們再來看看如何使用 wait/notify 來實現簡易版阻塞隊列,代碼如下:

import java.util.LinkedList;
/*** 自定義阻塞隊列類,使用 wait() 和 notifyAll() 方法實現線程同步。*/
public class MyBlockingQueueForWaitNotify {/*** 隊列的最大容量*/private int maxSize;/*** 存儲隊列元素的鏈表*/private LinkedList<Object> storage;/*** 構造函數,初始化隊列的最大容量和存儲鏈表。** @param size 隊列的最大容量*/public MyBlockingQueueForWaitNotify (int size) {// 將傳入的最大容量賦值給類的成員變量this.maxSize = size;// 初始化存儲鏈表storage = new LinkedList<>();}/*** 向隊列中添加一個元素。如果隊列已滿,則線程進入等待狀態。** @throws InterruptedException 如果線程在等待過程中被中斷*/public synchronized void put() throws InterruptedException {// 當隊列已滿時,當前線程進入等待狀態while (storage.size() == maxSize) {this.wait();}// 向隊列中添加一個新元素storage.add(new Object());// 喚醒所有等待的線程this.notifyAll();}/*** 從隊列中取出一個元素。如果隊列為空,則線程進入等待狀態。** @throws InterruptedException 如果線程在等待過程中被中斷*/public synchronized void take() throws InterruptedException {// 當隊列為空時,當前線程進入等待狀態while (storage.size() == 0) {this.wait();}// 從隊列中移除并打印第一個元素System.out.println(storage.remove());// 喚醒所有等待的線程this.notifyAll();}
}

如代碼所示,最主要的部分仍是 put 與 take 方法。我們先來看 put 方法,該方法被 synchronized 保護,while 檢查 List 是否已滿,如果不滿就往里面放入數據,并通過 notifyAll() 喚醒其他線程。同樣,take 方法也被 synchronized 修飾,while 檢查 List 是否為空,如果不為空則獲取數據并喚醒其他線程。

在生產/消費者模式中,有對這兩段代碼的詳細講解,遺忘的小伙伴可以到前面復習一下。

Condition 和 wait/notify的關系

對比上面兩種實現方式的 put 方法,會發現非常類似,此時讓我們把這兩段代碼同時列在屏幕中,然后進行對比:

public void put(Object o) throws InterruptedException {lock.lock();try {while (queue.size() == max) {notFull.await();}queue.add(o);notEmpty.signalAll();} finally {lock.unlock();}
}
public synchronized void put() throws InterruptedException {while (storage.size() == maxSize) {this.wait();}storage.add(new Object()); this.notifyAll();
}

可以看出,左側是 Condition 的實現,右側是 wait/notify 的實現:

  • lock.lock() 對應進入 synchronized 方法
  • condition.await() 對應 object.wait()
  • condition.signalAll() 對應 object.notifyAll()
  • lock.unlock() 對應退出 synchronized 方法

實際上,如果說 Lock 是用來代替 synchronized 的,那么 Condition 就是用來代替相對應的 Object 的wait/notify/notifyAll,所以在用法和性質上幾乎都一樣。

Condition 把 Object 的 wait/notify/notifyAll 轉化為了一種相應的對象,其實現的效果基本一樣,但是把更復雜的用法,變成了更直觀可控的對象方法,是一種升級。

await 方法會自動釋放持有的 Lock 鎖,和 Object 的 wait 一樣,不需要自己手動釋放鎖。

另外,調用 await 的時候必須持有鎖,否則會拋出異常,這一點和 Object 的 wait 一樣。

總結

Condition 是對 wait/notify 的改進和擴展,提供了更高的靈活性和可讀性。如果需要更復雜的線程通信機制,建議使用 Condition;如果場景簡單,可以繼續使用 wait/notify。

下面我們梳理總結一下,核心異同,以及各自適用場景:

相似點

目的相同:兩者都是用于實現線程間的通信和同步。它們允許一個線程等待某個條件滿足,然后由另一個線程通知它條件已經滿足,從而繼續執行。

等待和通知機制:都涉及線程進入等待狀態,然后被其他線程通知喚醒。在等待期間,線程會釋放鎖,以便其他線程可以進入同步塊修改共享狀態。

線程通信:兩者都用于線程間的通信,允許線程等待或喚醒其他線程。

需要鎖:兩者都需要與鎖配合使用,wait/notify 依賴 synchronized,而 Condition 依賴 Lock。

不同點

特性wait/notifyCondition
鎖的管理隱式鎖(通過 synchronized)顯式鎖(通過 Lock)
靈活性較低,只能有一個等待隊列較高,可以有多個條件變量(多個等待隊列)
可讀性較低,代碼容易變得復雜較高,代碼更清晰
中斷處理不支持中斷支持中斷(awaitUninterruptibly() 等)
等待條件無法指定多個條件可以指定多個條件(newCondition())

適用場景

wait/notify:適用于簡單的線程通信場景。

在 Java 中,wait、notify和notifyAll是Object類的方法。當一個線程調用一個對象的wait方法時,它會進入等待狀態,直到另一個線程調用同一個對象的notify或notifyAll方法。通常在使用synchronized關鍵字實現同步的時候使用。例如,一個線程在同步塊中調用wait方法等待某個條件,另一個線程在同步塊中改變了這個條件后調用notify或notifyAll方法通知等待的線程。

Condition:適用于復雜的線程通信場景,尤其是需要多個條件變量的場景。

在 Java 中,Condition是在java.util.concurrent.locks包下的一個接口,它是對傳統的對象監視器方法(如wait、notify和notifyAll)的一種替代,用于更靈活地實現線程間的通信和等待。通常在使用ReentrantLock實現同步的時候,配合Condition來實現線程間的等待和通知。比如,當一個線程需要等待某個條件滿足時,它可以調用Condition的await方法進入等待狀態,直到另一個線程調用signal或signalAll方法來通知它條件已經滿足。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/76014.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/76014.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/76014.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

29_項目

目錄 http.js 1、先注冊賬號 register.html 2、再登錄 login.html 3、首頁 index.html 4 詳情 details.html cart.html css index.css register.css details.css 演示 進階 http.js let baseURL "http://localhost:8888"; let resgiterApi baseURL &…

Next.js 項目生產構建優化

Next.js 項目生產構建優化的完整教程&#xff0c;涵蓋配置、工具鏈和性能調優技巧&#xff0c;助你顯著加速 npm run build&#xff1a; 注&#xff1a;學習階段請先測試環境使用&#xff01; 文章目錄 [toc]一、基礎優化1. 確保使用最新版本2. 清理無用依賴和代碼3. 配置 nex…

【嵌入式學習3】多任務編程

目錄 1、多任務 并發&#xff1a;在一段時間內交替去執行任務 并行&#xff1a; 2、線程 進程與線程 守護線程 1、多任務 在同一時間內執行多個任務&#xff0c;多任務分為并發和并行兩種形式 并發&#xff1a;在一段時間內交替去執行任務 軟件1執行0.01秒&#xff0c;切…

鏈路聚合(Link Aggregation)

目錄 一. 鏈路聚合概述 1. 基本概念 2. 實現條件 3. 成員接口和成員鏈路 二. 鏈路聚合模式 1. 手工模式 2. LACP模式 三. 負載分擔 1. 基于數據包的負載分擔 2. 基于數據流的負載分擔 一. 鏈路聚合概述 1. 基本概念 鏈路聚合&#xff08;Link Aggregation&#xff…

QT圖片輪播器(QT實操學習2)

1.項目架構 1.UI界面 2.widget.h? #ifndef WIDGET_H #define WIDGET_H#include <QWidget>#define TIMEOUT 1 * 1000 QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent n…

【HTML5】02-列表 + 表格 + 表單

本文介紹 列表、表格、表單的具體使用。 目錄 1. 列表 1.1 無序列表 1.2 有序列表 1.3 定義列表 2. 表格 2.1 基本使用 2.2 表格結構標簽 2.3 合并單元格 3. 表單 3.1 input標簽 3.2 input 標簽占位文本 3.3 單選框 3.4 上傳文件 3.5 多選框 3.6 下拉菜單 3.7…

【數據結構】導航

【數據結構】-CSDN博客 【數據結構】next數組、nextval數組-CSDN博客

開源項目里的 autogen.sh 是做什么?

./autogen.sh 是一個在基于 Autotools 構建系統的開源項目中常見的腳本。它的主要作用是準備構建環境&#xff0c;生成后續編譯所需的關鍵文件。 更具體地說&#xff0c;./autogen.sh 通常會執行以下操作&#xff1a; 檢查構建工具: 它會檢查系統中是否安裝了構建項目所需的工…

RabbitMQ高級特性--發送方確認

目錄 1. confirm確認模式 1.配置RabbitMQ 2.設置確認回調邏輯并發送消息 2.Return退回模式 1.配置RabbitMQ 2.設置返回回調邏輯并發送消息 在使用RabbitMQ的時候, 可以通過消息持久化來解決因為服務器的異常崩潰而導致的消息丟失, 但是還有?個問題, 當消息的生產者將消息發送出…

Python的ASGI Web 服務器之uvicorn

文章目錄 什么是uvicornUvicorn 和 uWSGI 對比區別安裝 Uvicorn使用示例 什么是uvicorn 官網https://www.uvicorn.org/ Uvicorn 是一個用于 Python 的 ASGI Web 服務器實現。 Until recently Python has lacked a minimal low-level server/application interface for async…

MongoDB 創建數據庫

MongoDB 創建數據庫 引言 MongoDB 是一款高性能、可擴展的 NoSQL 數據庫&#xff0c;廣泛應用于大數據領域。在 MongoDB 中&#xff0c;創建數據庫是進行數據存儲的第一步。本文將詳細介紹 MongoDB 數據庫的創建方法&#xff0c;包括手動創建和自動創建兩種方式。 MongoDB 數…

并發編程之最小化共享

文章目錄 **什么是「最小化共享」&#xff1f;****為什么要最小化共享&#xff1f;****如何實現最小化共享&#xff1f;****1. 線程局部存儲&#xff08;Thread-Local Storage&#xff09;****2. 消息傳遞&#xff08;Message Passing&#xff09;****3. 不可變數據&#xff08…

通信之光纖耦合器

以下是關于光纖耦合器的詳細介紹&#xff1a; 定義與原理 - 定義&#xff1a;光纖耦合器是一種能使傳輸中的光信號在特殊結構的耦合區發生耦合&#xff0c;并進行再分配的器件&#xff0c;也叫分歧器、連接器、適配器、光纖法蘭盤。 - 原理&#xff1a;利用不同光纖面緊鄰光纖芯…

自然語言模型的演變與未來趨勢:從規則到多模態智能的跨越

自然語言模型的演變與未來趨勢&#xff1a;從規則到多模態智能的跨越 自然語言處理(NLP)作為人工智能領域最具挑戰性的分支之一&#xff0c;在過去幾十年經歷了翻天覆地的變化。從最初基于規則的系統到如今擁有萬億參數的大型語言模型(LLMs)&#xff0c;這一技術革新不僅徹底改…

筆記本電腦更換主板后出現2203:System configuration is invalid,以及2201、2202系統錯誤的解決

筆記本電腦更換主板后啟動出現2203:System configuration is invalid,以及2201、2202系統錯誤的解決 自用的一臺ThinkpadT490筆記本電腦 ,由于主板故障,不得不更換主板,通過某寶購置主板后進行了更換。 具體拆卸筆記本可搜索網絡視頻教程。 注意: 在更換主板時,注意先拍…

JavaScript中的觀察者模式

以下是關于 觀察者模式(Observer Pattern) 的全面梳理,涵蓋核心概念、實現方式、應用場景及注意事項,幫助我們掌握這一解耦事件通知與處理的經典設計模式: 一、觀察者模式基礎 1. 核心概念 定義:定義對象間 一對多 的依賴關系,當被觀察對象(Subject)狀態變化時,自動…

RAG基建之PDF解析的“流水線”魔法之旅

將PDF文件和掃描圖像等非結構化文檔轉換為結構化或半結構化格式是人工智能的關鍵部分。然而,由于PDF的復雜性和PDF解析任務的復雜性,這一過程顯得神秘莫測。 在RAG(Retrieval-Augmented Generation)基建之PDF解析的“魔法”與“陷阱”中,我們介紹了PDF解析的主要任務,對現…

【Linux】GDB調試指南

一、GDB基礎 1. 啟動調試 gdb ./your_program # 啟動調試 gdb --args ./prog arg1 # 帶參數啟動 gdb -p <pid> # 附加到正在運行的進程 2. 斷點管理 b main # 在main函數設斷點 b file.c:20 # 在file.c第20行設斷點 b *0x4005a…

Android面試總結之Glide源碼級理解

當你的圖片列表在低端機上白屏3秒、高端機因內存浪費導致FPS腰斬時&#xff0c;根源往往藏在Glide的內存分配僵化、磁盤混存、網絡加載無優先級三大致命缺陷中。 本文從阿里P8級緩存改造方案出發&#xff0c;結合Glide源碼實現動態內存擴容、磁盤冷熱分區、智能預加載等黑科技&…