條件鎖

ReentrantLock類有一個方法newCondition用來生成這個鎖對象的一個條件(ConditionObject)對象,它實現了Condition接口。

Condition提供了線程通訊的一套機制await和signal等線程間進行通訊的方法。。


1、適用場景
? ? ?當某線程獲取了鎖對象,但由于某些條件沒有滿足,須要在這個條件上等待,直到條件滿足才可以往下繼續運行時。就須要用到條件鎖。

? ? ?這樣的情況下,線程主動在某條件上堵塞,當其他線程發現條件發生變化時,就能夠喚醒堵塞在此條件上的線程。

2、使用演示樣例
? ? ?以下是來自JDK的一段演示樣例代碼,須要先獲得某個鎖對象之后,才干調用這個鎖的條件對象進行堵塞。

? ? ?
class BoundedBuffer {final Lock lock = new ReentrantLock();final Condition notFull  = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100];int putptr, takeptr, count;public void put(Object x) throws InterruptedException {lock.lock(); try {while (count == items.length)notFull.await();items[putptr] = x;if (++putptr == items.length) putptr = 0;++count;notEmpty.signal();} finally {lock.unlock();}}public Object take() throws InterruptedException {lock.lock();try {while (count == 0)notEmpty.await();Object x = items[takeptr];if (++takeptr == items.length) takeptr = 0;--count;notFull.signal();return x;} finally {lock.unlock();}}}
注意上面的代碼,先是通過lock.lock獲得了鎖對象,然后發現條件不滿足時(count==items.length),緩存已滿,無法繼續往里面寫入數據,這時候就調用條件對象notFull.await()進行堵塞。

假設條件滿足,就會往緩存中寫入數據,同一時候通知等待緩存非空的線程,notEmpty.signal.

這樣就實現了讀線程和寫線程之間的通訊

3、線程堵塞對鎖的影響
? ? ?上面的樣例中。線程是先獲得了鎖對象之后。然后調用notFull.await進行的線程堵塞。在這樣的情況下,擁有鎖的線程進入堵塞,是否可能會造成死鎖。
? ? ?
? ? ?答案當然是否定的。

由于線程在調用條件對象的await方法中,首先會釋放當前的鎖,然后才讓自己進入堵塞狀態,等待喚醒。


4、線程的條件等待、喚醒與鎖對象的關系
? ? ?在ReentrantLock解析中說過。AbstractQueuedSynchronizer的內部維護了一個隊列,等待該鎖的線程是在這個隊列中。類似的,ConditionObject內部也是維護了一個隊列,等待該條件的線程也構成了一個隊列。

? ? ?當現成調用await進入堵塞時。便會增加到ConditionObject內部的等待隊列中。

注意,這里是自動進入堵塞。除非被其他線程喚醒或者被中斷,否則線程將一直堵塞下去。


? ? ?當其他線程調用signal喚醒堵塞的線程時,便把等待隊列中的第一個節點從隊列中移除,同一時候把節點增加到AbstractQueuedSynchronizer 鎖對象內的等待隊列中。為什么是進入到鎖的等待隊列中?由于線程被喚醒之后,并不意味著就能立馬運行。

此時,其他線程有可能正好擁有這個鎖,前面也已經有現成在等待這個鎖,所以被喚醒的線程須要進入鎖的等待隊列中,在前面的線程運行完畢后,才干繼續興許的操作。


? ? ?可參考下圖
? ? ?
? ? ?
5、線程能否同一時候處于條件對象的等待隊列中和鎖對象的等待隊列中

? ? ?不能。

線程僅僅有調用條件對象的await方法,才干進入這個條件對象的等待隊列中。而線程在調用await方法的前提是線程已經獲取了鎖,所以線程是在擁有鎖的狀態下進入條件對象的等待隊列的。擁有鎖的線程也就是正在執行的線程,是不在鎖對象的等待隊列中的。

? ? ?僅僅有當一個線程試著獲取鎖的時候。而這個鎖正好又由其他線程占領的時候。線程才會進入鎖的等待隊列中,等待擁有鎖的線程運行完畢。釋放鎖的時候被喚醒。

6、實現原理

? ? ?相關代碼在AbstractQueuedSynchronizer的內部類ConditionObject中能夠看到。

? ? ?ConditionObject有兩個屬性firstWaiter和lastWaiter,分別指向的是這個條件對象等待隊列的頭和尾。
? ? ?隊列的各個節點都是Node(AbstractQueuedSynchronizer的內部類)對象,通過Node對象的nextWaiter之間進行向下傳遞,所以,條件對象的等待隊列是一個單向鏈表。


以下是await的源碼
?? ? ??public?final?void?await?()?throws?InterruptedException {
????????????if?(Thread.interrupted())
????????????????throw?new?InterruptedException();
??????????? Node node = addConditionWaiter();
????????????int?savedState = fullyRelease(node);
????????????int?interruptMode = 0;
????????????while?(!isOnSyncQueue(node)) {
??????????????? LockSupport.?park(this);
????????????????if?((interruptMode = checkInterruptWhileWaiting(node)) != 0)
????????????????????break;
??????????? }
????????????if?(acquireQueued(node, savedState) && interruptMode !=?THROW_IE)
??????????????? interruptMode =?REINTERRUPT;
????????????if?(node.nextWaiter?!=?null)
??????????????? unlinkCancelledWaiters();
????????????if?(interruptMode != 0)
??????????????? reportInterruptAfterWait(interruptMode);
??????? }

首先是調用addConditionWaiter把當前線程增加到條件對象的等待隊列中,然后fullyRelease來釋放鎖,然后通過isOnSyncQueue來檢查當前線程節點是否在鎖對象的等待隊列中。
為什么要做這個檢查?由于線程被signal喚醒的時候,是首先增加到鎖對象的等待隊列中的。

假設沒有在鎖對象的等待隊列中,那么說明事件還沒有發生(也就是沒有signal方法沒有被調用)。所以線程須要堵塞來等待被喚醒。


在addConditionWaiter方法中完畢了等待隊列的構建過程,代碼例如以下

private?Node?addConditionWaiter() {
??????????? Node t =?lastWaiter;
????????????// If lastWaiter is cancelled, clean out.
????????????if?(t !=?null?&& t.waitStatus?!= Node.?CONDITION) {
??????????????? unlinkCancelledWaiters();
??????????????? t =?lastWaiter;
??????????? }
??????????? Node node =?new?Node(Thread.currentThread(), Node.?CONDITION);
????????????if?(t ==?null?)
????????????????firstWaiter?= node;
????????????else
??????????????? t.?nextWaiter?= node;
????????????lastWaiter?= node;
????????????return?node;
??????? }
線程增加隊列的順序與增加的時間一致,剛增加的線程是在隊列的最后面。

以下來看線程的喚醒
?public?final?void?signal() {
????????????if?(!isHeldExclusively())
????????????????throw?new?IllegalMonitorStateException();
??????????? Node first =?firstWaiter;
????????????if?(first !=?null)
??????????????? doSignal(first);
??????? }

喚醒操作實際上是通過doSignal完畢。注意這里傳遞的是firstWaiter指向的節點,也就是喚醒的時候,是從隊列頭開始喚醒的。
從尾部進入,從頭部喚醒。所以這里的等待隊列是一個FIFO隊列。


private?void?doSignal?(Node first) {
????????????do?{
????????????????if?( (firstWaiter?= first.nextWaiter) ==?null)
????????????????????lastWaiter?=?null?;
??????????????? first.?nextWaiter?=?null?;
??????????? }?while?(!transferForSignal(first) &&
???????????????????? (first =?firstWaiter) !=?null?);
??????? }

doSignal方法把第一個節點從條件對象的等待隊列中移除,然后終于是走到transferForSignal中來進行操作。
final?boolean?transferForSignal?(Node node) {
????????/*
???????? * If cannot change waitStatus, the node has been cancelled.
???????? */
????????if?(!compareAndSetWaitStatus(node, Node.?CONDITION, 0))
????????????return?false?;

????????/*
???????? * Splice onto queue and try to set waitStatus of predecessor to
???????? * indicate that thread is (probably) waiting. If cancelled or
???????? * attempt to set waitStatus fails, wake up to resync (in which
???????? * case the waitStatus can be transiently and harmlessly wrong).
???????? */
??????? Node p = enq(node);
????????int?ws = p.waitStatus?;
????????if?(ws > 0 || !compareAndSetWaitStatus(p, ws, Node.?SIGNAL))
??????????? LockSupport.?unpark(node.thread);
????????return?true?;
??? }

通過enq方法,把線程所在的節點增加到鎖對象的等待隊列中,這樣在條件合適的時候,線程被喚醒,獲得鎖,然后運行。

轉載于:https://www.cnblogs.com/liguangsunls/p/7360190.html

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

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

相關文章

計算機應用技術 平面設計,全國信息化計算機應用技術水平教育考試試卷 平面設計師...

科目編號:4233全國信息化計算機應用技術水平教育考試試卷(考試時間:180分鐘 考試總分:100分 專業認證課程:Photoshop 平面設計)注意事項1、 請首先按要求在試卷的標封處填寫您的姓名、考號等;2、 請仔細閱讀各種題目的…

RabbitMQ之消息模式簡單易懂,超詳細分享

前言上一篇對RabbitMQ的流程和相關的理論進行初步的概述,如果小伙伴之前對消息隊列不是很了解,那么在看理論時會有些困惑,這里以消息模式為切入點,結合理論細節和代碼實踐的方式一起來學習。正文常用的模式有Simple、Work、Fanout…

每天一個linux命令(6):rmdir 命令

今天學習一下linux中命令: rmdir命令。rmdir是常用的命令,該命令的功能是刪除空目錄,一個目錄被刪除之前必須是空的。(注意,rm - r dir命令可代替rmdir,但是有很大危險性。)刪除某目錄時也必須具…

jvm系列(八):jvm知識點總覽

在江湖中要練就絕世武功必須內外兼備,精妙的招式和深厚的內功,武功的基礎是內功。對于武功低(就像江南七怪)的人,招式更重要,因為他們不能靠內功直接去傷人,只能靠招式,利刃上優勢來…

計算機基礎知識的文獻,四?計算機文獻檢索基礎知識(原理、結構和功能)

1.計算機檢索原理計算機一方面接受用戶的檢索提問,一方面從數據庫中讀取文獻記錄,然后把兩者進行比較,即檢索提問標識與文獻記錄標識進行匹配運算,如果比較的結果一致,那么這篇文獻就會作為命中文獻在檢索結果中顯示&a…

APP地推心得:可復制的APP地推方案

APP地推難?APP地推方案包含哪些?現在,不需要編程就能自己完成手機APP制作,而且還有大量的APP模板,可以直接套用。APP的制作資金技術大幅度降低,現在最大的問題就是怎么APP推廣的問題。 在移動互聯網的時代&…

【代碼筆記】iOS-播放從網絡上下載的語音

代碼&#xff1a; ViewController.m #import "ViewController.h" //錄音 #import <AVFoundation/AVFoundation.h>interface ViewController () {//播放器AVAudioPlayer *player; }endimplementation ViewController- (void)viewDidLoad {[super viewDidLoad];/…

C# 基于.NET6的CM+Fody+HC入門實戰項目(經典)

概述上期我們概述了CMFodyHC&#xff0c;如果之前沒有閱讀&#xff0c;可以先了解下&#xff1a;C# 為什么說CMFodyHC是WPF開發的最強組合&#xff1f;今天基于最新的VS版本、最新的CM框架版本&#xff0c;.NET基于6.0&#xff0c;搭建了一個WPF入門學習項目實例&#xff0c;關…

PHP資源列表(轉)

一個PHP資源列表&#xff0c;內容包括&#xff1a;庫、框架、模板、安全、代碼分析、日志、第三方庫、配置工具、Web 工具、書籍、電子書、經典博文等等。 初始翻譯信息來自&#xff1a;《推薦&#xff01;國外程序員整理的 PHP 資源大全》 該內容也可以在github的相關項目上瀏…

當今 計算機已進入千家萬戶英語,學生英語教學論文,關于信息技術在大學英語教學中的應用探析相關參考文獻資料-免費論文范文...

導讀:這是一篇與學生英語教學論文范文相關的免費優秀學術論文范文資料,為你的論文寫作提供參考。摘 要&#xff1a;隨著經濟全球化和信息化時代的到來,計算機技術已深入到社會的各個領域.以往大學中所使用的傳統面授課教學模式已經跟不上信息時代的發展步伐,不能滿足當今教學的…

POJ - 2187 Beauty Contest(最遠點對)

http://poj.org/problem?id2187 題意 給n個坐標&#xff0c;求最遠點對的距離平方值。 分析 模板題&#xff0c;旋轉卡殼求求兩點間距離平方的最大值。 #include<iostream> #include<cmath> #include<cstring> #include<queue> #include<vector>…

Kong入門學習實踐(2)實驗環境搭建

【API網關】| 總結/Edison Zhou最近在學習Kong網關&#xff0c;因此根據老習慣&#xff0c;我會將我的學習過程記錄下來&#xff0c;一來體系化整理&#xff0c;二來作為筆記供將來翻看。由于我司會直接使用Kong企業版&#xff0c;學習過程中我會使用Kong開源版。本篇&#xff…

Mysql-索引的基礎和類型

一、 索引的基礎 索引類似于書籍的目錄&#xff0c;要想找到一本書的某個特定主題&#xff0c;需要先查找書的目錄&#xff0c;定位對應的頁碼。 存儲引擎使用類似的方式進行數據查詢&#xff0c;先去索引當中找到對應的值&#xff0c;然后根據匹配的索引找到對應的數據行 二…

ligerUI的列頭合并代碼片段

//列頭合并 function onAfterShowData(data){//顯示數據前觸發此事件 console.log(123); var k 0; var tr $(.l-grid-body.l-grid-body1).find(table tr);//找到被凍結的列&#xff08;frozen&#xff09;,利用find方法找到所有的行 $.each($(tr)…

我的未來計算機作文,我的未來作文(精選4篇)

我的未來作文(精選4篇)在平平淡淡的日常中&#xff0c;大家總免不了要接觸或使用作文吧&#xff0c;作文根據體裁的不同可以分為記敘文、說明文、應用文、議論文。怎么寫作文才能避免踩雷呢&#xff1f;以下是小編收集整理的我的未來作文&#xff0c;僅供參考&#xff0c;大家一…

RDS for MySQL Mysqldump常見問題及處理

2019獨角獸企業重金招聘Python工程師標準>>> 摘要&#xff1a; RDS for MySQL Mysqldump 常見問題和處理 GTID 特性相關 避免表級鎖等待 設置導出字符集 其他導出時需要注意的選項 舉例 RDS for MySQL 不支持的選項 RDS for MySQL 邏輯備份 1. GTID 特性相關 MySQ…

AI求解PDE

一、波動方程的PINN解法: Guo Y, Cao X, Liu B, et al. Solving partial differential equations using deep learning and physical constraints[J]. Applied Sciences, 2020, 10(17): 5917. 二、二維的Navier–Stokes方程組的PINN解法 矢量形式的不可壓縮Navier-Stokes方程…

使用CADisplayLink實現UILabel動畫特效

在開發時&#xff0c;我們有時候會遇到需要定時對UIView進行重繪的需求&#xff0c;進而讓view產生不同的動畫效果。 本文項目 效果圖 初探 CADisplayLink 定時對View進行定時重繪可能會第一時間想到使用NSTimer&#xff0c;但是這樣的動畫實現起來是不流暢的&#xff0c;因為在…

《ASP.NET Core 6框架揭秘》實例演示[27]:ASP.NET Core 6 Minimal API的模擬實現

Minimal API僅僅是在基于IHost/IHostBuilder的服務承載系統上作了小小的封裝而已&#xff0c;它利用WebApplication和WebApplicationBuilder這兩個類型提供了更加簡潔的API&#xff0c;同時提供了與現有API的兼容。[本文節選《ASP.NET Core 6框架揭秘》第17章]一、基礎模型二、…

Mysql的關聯查詢語句

一 內連接( inner join&#xff09; 1、多表中同時符合某種條件的數據記錄的集合 (取兩表公共部分) 2、inner join 可以縮寫成 join 例如: select * from A,B WHERE A.idB.id 或者 select * from A inner join B on A.idB.id 內連接分為三類:{ &#xff08;1&#xff0…