常見鎖策略之可重入鎖VS不可重入鎖

可重入鎖VS不可重入鎖

有一個線程,針對同一把鎖,連續加鎖兩次,如果產生了死鎖,那就是不可重入鎖,如果沒有產生死鎖,那就是可重入鎖.

死鎖

我們之前引入多線程的時候不是講了一個加數字的案例么,我們今天以它來舉例

當我們這樣寫的時候會出現什么問題?

分析:第一個synchronized的加鎖對象是 this ,當我們繼續執行代碼,就會發現第二個synchronized的加鎖對象還是 this ,此時就需要注意,當一個對象已經被加鎖了,此時嘗試對這個已經加鎖的對象再一次的進行加鎖,就會出現"鎖競爭",我們要想使得第二個synchronized實現對 this 的加鎖,就要讓increase執行完畢,但是要想讓increase執行完畢,就需要第二個synchronized加鎖成功,此時就陷入了循環,就出現了矛盾.此時這個代碼就卡在這里了,因此這個線程就僵住了.這是死鎖的第一個體現形式.

這里的關鍵在于,兩次加鎖,都是"同一個線程",第二次嘗試加鎖的時候,該線程已經有了這個鎖的權限了,這個時候,不應該加鎖失敗的,不應該阻塞等待的.

如果是一個不可重入鎖,這把鎖不會保存,是哪個線程對它加的鎖,只要它當前處于加鎖狀態之后,收到了"加鎖"這樣的請求,就會拒絕當前加鎖,而不管當下的線程是哪個,就會產生死鎖.

如果是一個可重入鎖,則是會讓這個鎖保存,是哪個線程加上的鎖,后續收到加鎖請求之后,就會先對比一下,看看加鎖的線程是不是當前自己持有這把鎖的線程,這個時候就可以靈活判定了.

但慶幸的是,synchronized本身就是一個可重入鎖,實際上我們上述的那個舉例子的代碼并不會出現死鎖的情況.

首先,答案是肯定的,不能釋放,如果在這里釋放鎖了,那么中間的synchronized以及中間的代碼就不會受到鎖的保護了.

那么我們想一下,可重入鎖,需要比不可重入鎖額外多出哪些功能

1.判斷當前加鎖的線程是不是同一個線程

2.判斷當前代碼執行到的是第幾層的鎖,那么這個功能是怎么實現的呢?

其實很簡單,持有一個"計數器"就可以了,讓鎖對象不光要記錄是哪個線程持有鎖,同時再通過一個整形變量記錄當前這個線程加了幾次鎖,每遇到一個加鎖操作,就計數器+1,每遇到一個解鎖操作就-1,當計數器減為0的時候,才真正執行釋放鎖的操作,其他時候不釋放鎖.而類似于這個操作的操作,我們稱它為"引用計數"

死鎖的三種典型情況

1.一個線程,一把鎖,但是是不可重入鎖,該線程針對這個鎖連續加鎖兩次,就會出現死鎖

2.兩個線程,兩把鎖,這兩個線程先分別獲取到一把鎖,然后再同時嘗試獲取對方的鎖

下面我們敲代碼來理解一下死鎖

首先,這是一個錯誤的代碼

執行結果如下

實際上由剛才的分析可以知道,這里會出現死鎖的情況,那么為什么這里和理論值不一樣呢?

因為沒有加Sleep

代碼如上

執行結果如下

那么,為什么?為什么加了Sleep之后會不一樣,究竟是Sleep改變了線程原有的樣子,還是說Sleep恢復了線程原有的樣子?

答案是后者

我們在使用多線程的時候會發現,程序運行的時間特別長了會經常出現一些問題,或者說當我們來氣了多個線程他們分別執行幾個任務,但是因為執行的任務的時間非常短,有時候CPU切換的時候會出現一系列的問題.

原因是當我們設置Sleep是,就等于告訴CPU,當前的線程不再運行,持有當前對象的鎖.那么這個時候CPU就會切換到另外的線程了,這種操作在有些時候是非常好的.

3.N個線程M把鎖

哲學家就餐問題

每個哲學家,主要做兩件事

1.思考人生,會放下筷子

2.吃面.會拿起左手和右手的筷子

3.每個哲學家,什么時候思考人生,什么時候吃面條,都不好說

2.每個哲學家一旦想吃面條了,就會非常固執的完成吃面條的這個操作,如果此時,他的筷子被別人使用了,就會阻塞等待,而且等待的過程中不會放下手中已經拿著的的筷子

是否有辦法去避免死鎖呢?

先明確產生死鎖的原因,死鎖的必要條件

四個必要條件(缺一不可,只要破壞其中任意一個條件,就可以避免死鎖)

1.互斥使用,一個線程獲取到一把鎖之后,別的線程不能獲取到這個鎖(我們實際使用的鎖,一般都是互斥的(鎖的基本特性))

2.不可搶占,鎖只能是被持有者主動釋放,而不是被其他線程直接搶走(鎖的基本特性)

3.請求和保持,這一個線程嘗試去獲取多把鎖,在獲取第二把鎖的過程中,會保持對第一把鎖的獲取狀態(取決于代碼結構)(很可能會影響到需求)

在獲取第二把鎖的同時會保持對第一把鎖的狀態,這里由于獲取第二把鎖的時候,并沒有去釋放第一把鎖,所以就會出現阻塞等待

當我們將代碼改成這樣,即獲取完第一把鎖之后,并且將第一把鎖釋放掉,此時再去請求獲取第二把鎖,這樣做是不會出現死鎖的

4.循環等待.t1嘗試獲取 locker2,需要t2執行完,釋放locker2;t2嘗試獲取locker1,需要t1執行完,釋放locker1 (取決于代碼結構)(解決死鎖問題的最關鍵要點)

如果具體解決死鎖問題,實際的方法有很多種(例:銀行家算法(但不推薦,因為不接地氣))

介紹一個更簡單,也非常有效的解決死鎖的辦法

針對鎖進行編號,并且規定加鎖的順序

比如,約定,每個線程如果要獲取多把鎖,必須先獲取編號小的鎖,后獲取編號大的鎖.只要所有線程加鎖的順序,都嚴格遵守上述順序,就一定不會出現循環等待.

像這樣,我們規定先讓locker1加鎖,然后讓locker2加鎖,按照指定順序加鎖,也就可以避免死鎖的問題了

synchronized具體是采用了哪些鎖策略

1.synchronized即是悲觀鎖,也是樂觀鎖.

2.synchronized即是重量級鎖,也是輕量級鎖.

3.synchronized重量級鎖部分是基于系統的互斥鎖實現的,輕量級鎖部分是基于自旋鎖實現的.

4.synchronized是非公平鎖(不會遵守先來后到,鎖釋放了之后,哪個線程拿到鎖,各憑本事).

5.synchronized是可重入鎖(內部會記錄哪個線程拿到了鎖,記錄引用次數).

6.synchronized不是讀寫鎖.

synchronized內部實現策略(內部原理)

代碼中寫了一個synchronized之后,這里可能會產生一系列的"自適應的過程",鎖升級(鎖膨脹)

無鎖->偏向鎖->輕量級鎖->重量級鎖

偏向鎖(懶漢模式思想的延伸)

不是真的加鎖,而只是做了一個"標記".如果有別的線程來競爭鎖了,才會真的加鎖,如果沒有別的線程競爭,就自始至終都不會真的加鎖了.(加鎖本身,有一定的開銷,能不加就不加,非得是有人來競爭了,才會真的加鎖)

偏向鎖在沒有其他人競爭的時候,就僅僅是一個簡單的標記(非常輕量).一旦有別的線程嘗試加鎖,就會立刻把偏向鎖升級為一個真正的加鎖狀態,讓其他線程只能阻塞等待

輕量級鎖

synchronized通過自旋的方式來實現輕量級鎖,我這邊把鎖占據了,另一個線程就會按照自旋的方式,來反復查詢當前的鎖的狀態是不是被釋放了.但是,后續如果競爭這把鎖的線程越來越多了(鎖沖突更激烈了),就會從輕量級鎖,升級成重量級鎖

鎖消除

編譯器,會智能的判定,當前的這個代碼,是否有必要加鎖,如果你寫了加鎖,但實際上沒有必要加鎖,就會把加鎖操作自動優化掉。

比如在單個線程中使用StringBuffer.

編譯器進行優化,是要保證優化之后的邏輯和之前的邏輯是一致的

鎖粗化

關于"鎖的粒度"如果加鎖操作里包含的實際要執行的代碼越多,就認為鎖的粒度越大.

//以下是一些偽代碼
//鎖的粒度小
for(.....){synchronized(this){count++;    }
}
//鎖的粒度大
synchronized(this){for(.....){count++;    }
}

?

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

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

相關文章

前端基礎--Vue3

Vue3基礎 VUE3和VUE2的區別 2020年9月18日,Vue.js發布版3.0版本,代號:One Piece 于 2022 年 2 月 7 日星期一成為新的默認版本! Vue3性能更高,初次渲染快55%, 更新渲染快133% 。體積更小 Vue3.0 打包大小減少41%。 同時Vue3可以更好的支持T…

基于微服務智能推薦健康生活交流平臺的設計與實現(SpringCloud SpringBoot)+文檔

💗博主介紹💗:?在職Java研發工程師、專注于程序設計、源碼分享、技術交流、專注于Java技術領域和畢業設計? 溫馨提示:文末有 CSDN 平臺官方提供的老師 Wechat / QQ 名片 :) Java精品實戰案例《700套》 2025最新畢業設計選題推薦…

vb 學習簡介

vb 第一節 Visual Basic(簡稱VB)是一種高級編程語言,它最初由微軟公司開發,旨在簡化Windows應用程序的開發過程。下面,我們將介紹Visual Basic編程語言的基礎概念和用途,包括其歷史背景、主要特性以及在現代編程中的應用。 歷史背景 Visual Basic起源于1991年,當時微軟…

代碼隨想錄算法訓練營day72 | 117. 軟件構建、47. 參加科學大會

本次題目來自于卡碼網 117. 軟件構建(拓撲排序) python設置默認值 from collections import defaultdict aa defaultdict(int) 拓撲排序:找到入度為0的節點,然后移除。如果最后都能移除,則無環,可以排…

C#發票識別接口,再長的稅號錄入都不怕

“十二金”工程是我國政府在信息化建設中的重要一步,“金稅工程”總稱為中國稅收管理信息系統(CTAIS),是我國電子政務的核心系統之一,是財政的重要環節。十二金”是面向政府辦公業務建立的十二個重點信息應用系統,按“…

解決使用monaco-editor編譯器,編譯器展示內容沒有超過編譯器高度,但是出現滾動條問題

前言: 最近在完成項目時,有使用編譯器進行在線編輯的功能,就選用了monaco-editor編譯器,但是實現功能之后,發現即使在編譯器展示的內容沒有超過編譯器高度的情況下,編譯器依舊存在滾動條,會展示…

計算機網絡--網絡層

一、網絡層的服務和功能 網絡層主要為應用層提供端對端的數據傳輸服務 網絡層接受運輸層的報文段,添加自己的首部,形成網絡層分組。分組是網絡層的傳輸單元。網絡層分組在各個站點的網絡層之間傳輸,最終到達接收方的網絡層。接收方網絡層將運…

如何在 Java 應用中使用 Jedis 客戶端庫來實現 Redis 緩存的基本操作

本人詳解 作者:王文峰,參加過 CSDN 2020年度博客之星,《Java王大師王天師》 公眾號:JAVA開發王大師,專注于天道酬勤的 Java 開發問題中國國學、傳統文化和代碼愛好者的程序人生,期待你的關注和支持!本人外號:神秘小峯 山峯 轉載說明:務必注明來源(注明:作者:王文峰…

構建高效盲盒小程序:數據庫設計、安全策略與性能優化

在移動互聯網時代,盲盒經濟以其獨特的魅力迅速崛起,成為連接消費者與商品的新橋梁。盲盒小程序作為這一趨勢的載體,不僅要求用戶體驗流暢,還需確保數據安全與性能卓越。本文將從數據庫設計、安全策略及性能優化三個方面&#xff0…

堆與棧的概念(RTOS)

目錄 #堆在RTOS的概念 #相關代碼表示 #堆相關特點 #棧在RTOS中的概念 #棧的代碼表示 #棧的相關特點 #為什么每個RTOS任務都要有自己的棧 前言:本篇參考韋東山老師的RTOS,連接放在最后 #堆在RTOS的概念 本文所指的堆與棧并不是數據結構中&#xff…

【unity實戰】在Unity中使用有限狀態機制作一個敵人AI

最終效果 文章目錄 最終效果前言有限狀態機的主要作用和意義素材下載邏輯圖敵人動畫配置優雅的代碼文件目錄狀態機代碼定義敵人不同狀態切換創建敵人效果更多的敵人參考源碼完結 前言 有限狀態機以前的我嗤之以鼻,現在的我逐幀分析。其實之前我就了解過有限狀態機&…

2.(vue3.x+vite)調用iframe的方法(vue編碼)

1、效果預覽 2.編寫代碼 (1)主頁面 <template><div><button @click="sendMessage">調用iframe,并發送信息

【udp報文】udp報文未自動分片,報文過長被攔截問題定位

問題現象 某局點出現一個奇怪的現象&#xff0c;客戶端給服務端發送消息&#xff0c;服務端僅能收到小部分消息&#xff0c;大部分消息從客戶端發出后&#xff0c;服務端都未收到。 問題定位 初步分析 根據現象初步分析&#xff0c;有可能是網絡原因導致消息到服務端不可達&a…

【C語言】文件的順序讀寫

©作者:末央&#xff06; ©系列:C語言初階(適合小白入門) ©說明:以凡人之筆墨&#xff0c;書寫未來之大夢 目錄 前言字符輸入輸出函數 - fgetc和fputc文本行輸入輸出函數 - fgets和fputs格式化輸入輸出函數 - fscanf和fprintf 前言 對文件數據的讀寫可以分為順序…

Unity3D 打造基于AStar的尋路與導航詳解

在游戲開發中&#xff0c;尋路與導航是一個至關重要的功能&#xff0c;它能夠使游戲角色自動找到最優路徑&#xff0c;避開障礙物&#xff0c;實現自動導航&#xff0c;從而提升游戲體驗。AStar&#xff08;A*&#xff09;算法作為一種廣泛應用的尋路算法&#xff0c;因其高效性…

關于多線程的使用方法

多線程在python中應用比較廣泛&#xff0c;但是因為python中有GIL鎖的緣故&#xff0c;在多線程中看起來是并發的執行的&#xff0c;在宏觀上是并發執行的&#xff0c;但是在微觀上是一個接著一個執行。 在python中使用多線程比較簡單&#xff0c;是一套固定的模版。 from qu…

PHP利用GD庫實現圖片合成功能方法

在程序項目開發的過程中我們免不了要實現一種功能。例如海報的生成&#xff0c;照片和文字合成一張新的圖片。php中怎么實現 實現功能 文字和照片合成一張新的照片&#xff0c;并且自適應換行并加上簽名和日期&#xff0c;加上字體樣式&#xff0c;下面我們就開實現該功能 實現…

Seal^_^【送書活動第8期】——《ChatGLM3大模型本地化部署、應用開發與微調》

Seal^_^【送書活動第8期】——《ChatGLM3大模型本地化部署、應用開發與微調》 一、參與方式二、本期推薦圖書2.1 作者建語2.2 編輯推建2.3 圖書簡介2.4 前 言2.5 目 錄 三、正版購買 大模型領域 既是繁星點點的未知宇宙&#xff0c;也是蘊含無數可能的廣闊天地&#xff0c; 正…

深入理解 Linux 內核架構

目錄 引言內核概念Linux 內核的基本組成 進程管理內存管理文件系統設備驅動網絡棧內核結構 內核態與用戶態內核模塊系統調用中斷與異常處理內核同步機制Linux 內核使用場景常用的內核命令與工具內核調試與性能優化總結 1. 引言 Linux 內核是現代計算機系統的核心組件之一&am…

python--基礎知識點--協程

協程由用戶態控制&#xff0c;不由內核控制1個線程中可以開很多協程協程切換是在用戶態控制不由內核控制&#xff0c;切換時資源開銷小使用方式&#xff1a;async def、await可等待對象(協程對象、Future對象、task對象(是Future對象的子類)->io等待)、事件循環使用場景&…