在現(xiàn)代并發(fā)編程的迷宮中浮禾,鎖是保護(hù)數(shù)據(jù)完整性的守護(hù)者。從基礎(chǔ)的互斥鎖(Mutex)確保單一線程訪問蝉稳,到讀寫鎖(Read-Write Locks)平衡讀多寫少的場景描馅,再到樂觀鎖(Optimistic Locking)減少鎖的競爭,以及悲觀鎖(Pessimistic Locking)應(yīng)對高沖突環(huán)境更振,每種鎖都有其獨特的用武之地。而細(xì)粒度鎖(Fine-Grained Lock)則以其在更小的數(shù)據(jù)粒度上操作,進(jìn)一步優(yōu)化了并發(fā)控制土陪。本文將帶您一探這些鎖的神秘面紗,了解它們?nèi)绾螀f(xié)同工作以提升系統(tǒng)性能肴熏,同時確保數(shù)據(jù)安全鬼雀。無論您是資深開發(fā)者還是并發(fā)領(lǐng)域的新手,這些鎖的策略和實踐都將是您構(gòu)建高效蛙吏、穩(wěn)定系統(tǒng)的重要工具源哩。
肖哥彈架構(gòu) 跟大家“彈彈” 高并發(fā)鎖, 關(guān)公回 'mvcc' 獲得手寫數(shù)據(jù)庫事務(wù)代碼
歡迎 點贊鸦做,關(guān)注励烦,評論。
歷史熱點文章
- 28個驗證注解泼诱,通過業(yè)務(wù)案例讓你精通Java數(shù)據(jù)校驗(收藏篇)
- Java 8函數(shù)式編程全攻略:43種函數(shù)式業(yè)務(wù)代碼實戰(zhàn)案例解析(收藏版)
- 69 個Spring mvc 全部注解:真實業(yè)務(wù)使用案例說明(必須收藏)
- 24 個Spring bean 全部注解:真實業(yè)務(wù)使用案例說明(必須收藏)
- MySQL索引完全手冊:真實業(yè)務(wù)圖文講解17種索引運用技巧(必須收藏)
- 一個項目代碼講清楚DO/PO/BO/AO/E/DTO/DAO/ POJO/VO
1坛掠、細(xì)粒度鎖實現(xiàn)流程策略
細(xì)粒度鎖內(nèi)部實現(xiàn)思路
- 初始化鎖:在應(yīng)用程序啟動或資源創(chuàng)建時,初始化鎖對象治筒。
- 請求鎖:當(dāng)線程需要訪問受保護(hù)的資源時屉栓,請求鎖。
- 鎖是否可用:檢查鎖是否已經(jīng)被其他線程占用耸袜。
- 獲取鎖:如果鎖可用系瓢,當(dāng)前線程獲取鎖。
- 等待鎖釋放:如果鎖不可用句灌,線程進(jìn)入等待狀態(tài)夷陋,直到鎖被釋放。
- 執(zhí)行受保護(hù)的操作:線程在獲取鎖后執(zhí)行需要保護(hù)的操作胰锌。
- 操作是否完成:檢查受保護(hù)的操作是否已經(jīng)完成骗绕。
- 釋放鎖:操作完成后,線程釋放鎖资昧,允許其他線程獲取鎖酬土。
- 通知等待線程:釋放鎖后,如果有線程在等待格带,通知它們鎖已可用撤缴。
細(xì)粒度鎖的本質(zhì)思路是將鎖的粒度細(xì)化到最小必要的范圍刹枉,以減少鎖競爭和提高并發(fā)性能。以下是細(xì)粒度鎖的幾個核心原則和思路:
-
最小化鎖范圍:
- 細(xì)粒度鎖將鎖的作用范圍限制在最小的數(shù)據(jù)單元或操作上屈呕,而不是對整個數(shù)據(jù)結(jié)構(gòu)或資源加鎖微宝。
-
減少鎖持有時間:
- 通過快速完成操作并盡早釋放鎖,減少每個線程持有鎖的時間虎眨,從而減少其他線程的等待時間蟋软。
-
鎖分離:
- 將不同的操作或數(shù)據(jù)訪問分離到不同的鎖上,使得多個操作可以并行執(zhí)行嗽桩,而不是所有操作都依賴于同一個鎖岳守。
-
鎖分段:
- 將數(shù)據(jù)結(jié)構(gòu)分割成多個段,每個段有自己的鎖碌冶,這樣可以同時對不同段進(jìn)行操作而不會相互阻塞湿痢。
-
無鎖編程:
- 利用原子操作和數(shù)據(jù)結(jié)構(gòu)來避免使用鎖,通過CAS(Compare-And-Swap)等機制來保證數(shù)據(jù)的一致性扑庞。
-
讀寫鎖:
- 允許多個讀操作同時進(jìn)行蒙袍,但寫操作需要獨占鎖,以此來提高讀操作的并發(fā)性嫩挤。
-
鎖粗化:
- 在某些情況下害幅,如果一個線程需要連續(xù)訪問多個資源,可以考慮將鎖的范圍擴(kuò)大岂昭,以減少頻繁的鎖獲取和釋放以现。
-
鎖升級和降級:
- 根據(jù)實際情況動態(tài)調(diào)整鎖的粒度,例如從讀鎖升級到寫鎖约啊,或者在不同級別的鎖之間進(jìn)行切換邑遏。
-
避免死鎖:
- 設(shè)計鎖策略時,確保鎖的獲取和釋放順序一致恰矩,避免出現(xiàn)死鎖记盒。
-
性能監(jiān)控:
- 監(jiān)控鎖的性能,包括鎖的競爭率外傅、等待時間和吞吐量纪吮,以便根據(jù)實際情況調(diào)整鎖策略。
2萎胰、細(xì)顆粒鎖使用通用流程
細(xì)粒度鎖使用流程說明
- 資源訪問請求:線程請求訪問受保護(hù)的資源碾盟。
- 檢查鎖狀態(tài):線程檢查細(xì)粒度鎖的狀態(tài),確定是否可以獲取鎖技竟。
- 鎖定:如果鎖可用冰肴,線程獲取鎖并執(zhí)行資源操作。
- 執(zhí)行資源操作:線程對資源進(jìn)行操作,如讀取熙尉、修改等联逻。
- 等待鎖可用:如果鎖不可用,線程進(jìn)入等待狀態(tài)检痰,直到鎖被釋放包归。
- 操作完成:線程完成資源操作。
- 釋放鎖:線程釋放鎖攀细,允許其他等待的線程獲取鎖。
- 資源訪問者等待隊列:等待鎖的線程排隊等待爱态。
3谭贪、HikariCP在連接池中細(xì)顆粒鎖落地思路
細(xì)粒度鎖在連接池落地設(shè)計說明
- 連接池管理器:負(fù)責(zé)整個連接池的管理和調(diào)度。
- 連接對象池:存儲所有可用的數(shù)據(jù)庫連接對象锦担。
- 細(xì)粒度鎖:為每個連接或連接組提供獨立的鎖俭识,以減少鎖競爭。
- 連接狀態(tài):記錄每個連接的當(dāng)前狀態(tài)(如空閑洞渔、使用中套媚、失效等)。
- 連接屬性:存儲每個連接的配置和屬性磁椒。
- 連接操作:管理連接的創(chuàng)建堤瘤、使用和銷毀等操作。
- 應(yīng)用程序線程:請求和使用數(shù)據(jù)庫連接的應(yīng)用程序線程浆熔。
- 連接請求隊列:管理連接請求本辐,確保按順序分配連接。
- 應(yīng)用程序操作:應(yīng)用程序?qū)?shù)據(jù)庫連接進(jìn)行的操作医增。
- 連接釋放隊列:管理連接釋放請求慎皱,確保連接被正確回收。
- 監(jiān)控線程:定期檢查連接狀態(tài)和性能指標(biāo)叶骨,確保連接池的健康茫多。
4、細(xì)顆粒鎖使用案例
4.1. 讀寫鎖(ReadWriteLock)
ReadWriteLock
的本質(zhì)思路是允許多個讀操作同時進(jìn)行忽刽,但寫操作是獨占的天揖,以此來提高并發(fā)性能。這種設(shè)計模式通常用于讀多寫少的場景跪帝。
場景描述:在高并發(fā)的Web應(yīng)用中宝剖,緩存系統(tǒng)需要頻繁地讀取數(shù)據(jù),而寫入操作相對較少歉甚。使用讀寫鎖可以提高讀取操作的并發(fā)性万细,減少鎖的競爭。
應(yīng)用代碼
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class CacheService {
private final Map<String, Object> cache = new ConcurrentHashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public Object getValue(String key) {
lock.readLock().lock();
try {
return cache.get(key);
} finally {
lock.readLock().unlock();
}
}
public void setValue(String key, Object value) {
lock.writeLock().lock();
try {
cache.put(key, value);
} finally {
lock.writeLock().unlock();
}
}
}
ReadWriteLock 類設(shè)計
UML類圖中:
-
ReadWriteLock
是一個接口,定義了獲取讀鎖和寫鎖的方法赖钞。 -
ReentrantReadWriteLock
是ReadWriteLock
的具體實現(xiàn)腰素,它內(nèi)部維護(hù)了讀鎖和寫鎖的狀態(tài)。 -
ReentrantReadLock
和ReentrantWriteLock
是內(nèi)部類雪营,分別實現(xiàn)了讀鎖和寫鎖的行為弓千。 -
Lock
是一個接口,定義了鎖的基本操作献起,如lock()
洋访、unlock()
、tryLock()
等谴餐。 -
ReentrantReadWriteLock
包含了ReentrantReadLock
和ReentrantWriteLock
姻政,用于管理讀鎖和寫鎖。
ReadWriteLock設(shè)計思路
核心思路
-
讀鎖(Shared Lock) :
- 當(dāng)一個線程獲得讀鎖時岂嗓,它可以讀取數(shù)據(jù)汁展。
- 多個線程可以同時持有讀鎖,這意味著多個線程可以同時讀取數(shù)據(jù)厌殉,而不會互相阻塞食绿。
-
寫鎖(Exclusive Lock) :
- 當(dāng)一個線程獲得寫鎖時,它可以修改數(shù)據(jù)公罕。
- 寫鎖是獨占的器紧,這意味著在任何時候只能有一個線程持有寫鎖。
- 當(dāng)寫鎖被持有時楼眷,所有其他讀和寫請求都必須等待直到寫鎖被釋放品洛。
-
鎖升級和降級:
- 鎖升級:從讀鎖升級到寫鎖。這通常需要先釋放讀鎖摩桶,然后獲取寫鎖桥状。升級過程中可能會引入競態(tài)條件,需要謹(jǐn)慎處理硝清。
- 鎖降級:從寫鎖降級到讀鎖辅斟。這通常比較安全,因為寫鎖持有者在釋放寫鎖之前已經(jīng)確保了數(shù)據(jù)的一致性芦拿。
實現(xiàn)機制
-
鎖狀態(tài)管理:
- 鎖狀態(tài)通常由一個計數(shù)器來管理士飒,用于跟蹤當(dāng)前持有鎖的讀線程數(shù)量和是否有寫線程持有鎖。
-
讀鎖的獲取:
- 如果沒有線程持有寫鎖蔗崎,讀線程可以增加讀鎖計數(shù)器并立即獲取讀鎖酵幕。
- 如果有線程持有寫鎖,讀線程必須等待寫鎖釋放缓苛。
-
寫鎖的獲取:
- 寫線程必須等待所有讀鎖和寫鎖都被釋放后才能獲取寫鎖芳撒。
- 這通常涉及到阻塞等待,直到讀鎖計數(shù)器為零且沒有其他寫線程競爭。
-
鎖的釋放:
- 讀鎖的釋放涉及到減少讀鎖計數(shù)器笔刹。
- 寫鎖的釋放允許其他等待的讀或?qū)懢€程嘗試獲取鎖芥备。
-
鎖的公平性:
- 鎖可以是公平的或非公平的。
- 公平鎖確保等待時間最長的線程首先獲得鎖舌菜,而非公平鎖則允許線程搶占鎖萌壳。
-
鎖的重入:
- 讀鎖和寫鎖都支持重入,這意味著同一個線程可以多次獲取同一把鎖日月。
-
鎖的中斷:
- 鎖的獲取操作可以響應(yīng)中斷袱瓮,這意味著線程在等待鎖的過程中可以被中斷。
4.2. 分段鎖(Segmented Locks)
分段鎖(Segmented Locks)是一種用于提高并發(fā)性能的鎖策略爱咬,它通過將數(shù)據(jù)結(jié)構(gòu)分割成多個段(或分區(qū))尺借,并為每個段提供獨立的鎖,從而允許多個線程同時對不同段進(jìn)行操作台颠。這種策略特別適用于那些需要頻繁訪問的大型數(shù)據(jù)結(jié)構(gòu)褐望,如哈希表勒庄、數(shù)組或其他集合類型串前。
場景描述:在分布式數(shù)據(jù)庫系統(tǒng)中,數(shù)據(jù)被分割成多個片段(shards)实蔽,每個片段可以獨立地進(jìn)行操作荡碾。使用分段鎖可以確保對不同片段的操作不會相互干擾。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ShardedDatabase {
private final Map<Integer, Lock> locks = new ConcurrentHashMap<>();
private final Map<Integer, Map<String, Object>> shards = new ConcurrentHashMap<>();
public ShardedDatabase(int numShards) {
for (int i = 0; i < numShards; i++) {
locks.put(i, new ReentrantLock());
shards.put(i, new ConcurrentHashMap<>());
}
}
public void updateShard(int shardId, String key, Object value) {
locks.get(shardId).lock();
try {
shards.get(shardId).put(key, value);
} finally {
locks.get(shardId).unlock();
}
}
public Object getShardValue(int shardId, String key) {
locks.get(shardId).lock();
try {
return shards.get(shardId).get(key);
} finally {
locks.get(shardId).unlock();
}
}
}
Segmented Locks 類設(shè)計
圖形解釋:
-
SegmentedHashTable
:- 包含一個整型變量
segmentCount
局装,表示分段的數(shù)量坛吁。 - 包含一個
Segment
數(shù)組_segments
,每個元素代表一個數(shù)據(jù)段铐尚。 - 提供
getSegment
方法拨脉,根據(jù)鍵的哈希值確定應(yīng)該訪問哪個段。 - 提供
get
和put
方法宣增,用于客戶端訪問和修改哈希表玫膀。
- 包含一個整型變量
-
Segment
:- 包含一個
Lock
對象lock
,用于控制對該段的并發(fā)訪問爹脾。 - 包含一個
HashMap
對象map
帖旨,用于存儲該段的數(shù)據(jù)。 - 提供
get
和put
方法灵妨,用于在該段內(nèi)進(jìn)行數(shù)據(jù)的讀取和寫入操作解阅。
- 包含一個
-
關(guān)系:
-
SegmentedHashTable
包含多個Segment
對象,表示它是由多個段組成的泌霍。
-
Segmented Locks設(shè)計思路
核心思路
-
數(shù)據(jù)分段:
- 將數(shù)據(jù)結(jié)構(gòu)分割成多個邏輯段或分區(qū)货抄,每個段包含數(shù)據(jù)結(jié)構(gòu)的一部分。
- 每個段可以獨立于其他段進(jìn)行操作,減少了鎖的競爭碉熄。
-
獨立鎖:
- 為每個數(shù)據(jù)段分配一個獨立的鎖桨武。
- 當(dāng)一個線程需要操作某個數(shù)據(jù)段時,它只需獲取該段的鎖锈津,而不影響其他段的操作呀酸。
-
并發(fā)訪問:
- 允許多個線程同時訪問不同的數(shù)據(jù)段,因為每個段的鎖是獨立的琼梆。
- 這種設(shè)計顯著提高了并發(fā)性能性誉,尤其是在讀多寫少的場景中。
-
鎖粒度:
- 分段鎖通過減小鎖的粒度來減少鎖的競爭茎杂。
- 相比于全局鎖错览,分段鎖允許更細(xì)粒度的并發(fā)控制。
-
平衡性能與復(fù)雜性:
- 分段鎖增加了實現(xiàn)的復(fù)雜性煌往,因為需要管理多個鎖和數(shù)據(jù)段倾哺。
- 但是,它通常能提供更好的性能刽脖,特別是在高并發(fā)環(huán)境下羞海。
-
避免熱點:
- 在全局鎖的情況下,數(shù)據(jù)結(jié)構(gòu)的某些部分可能會成為熱點曲管,導(dǎo)致大量的線程競爭同一資源却邓。
- 分段鎖通過分散訪問壓力,減少了熱點問題院水。
-
動態(tài)調(diào)整:
- 根據(jù)實際的訪問模式和性能需求腊徙,可以動態(tài)地調(diào)整段的大小和數(shù)量。
- 這允許系統(tǒng)在運行時優(yōu)化性能檬某。
實現(xiàn)注意事項
-
鎖管理:
- 需要有效管理鎖的獲取和釋放撬腾,以避免死鎖和性能瓶頸。
-
數(shù)據(jù)一致性:
- 在跨段操作時恢恼,需要確保數(shù)據(jù)的一致性和完整性民傻。
- 可能需要額外的同步機制來處理涉及多個段的操作。
-
負(fù)載均衡:
- 需要合理分配數(shù)據(jù)到各個段厅瞎,以避免某些段過載而其他段空閑饰潜,這可能導(dǎo)致性能瓶頸。
-
擴(kuò)展性:
- 設(shè)計時需要考慮系統(tǒng)的擴(kuò)展性和簸,確保在增加更多的段或鎖時彭雾,系統(tǒng)的性能不會受到負(fù)面影響。
4.3. 無鎖數(shù)據(jù)結(jié)構(gòu)(Lock-Free Data Structures)
無鎖數(shù)據(jù)結(jié)構(gòu)(Lock-Free Data Structures)是一種并發(fā)編程技術(shù)锁保,旨在避免使用傳統(tǒng)的鎖機制來同步對共享數(shù)據(jù)的訪問薯酝。無鎖算法利用原子操作和內(nèi)存模型來保證數(shù)據(jù)的一致性和線程之間的協(xié)調(diào)半沽,從而減少鎖帶來的開銷和潛在的死鎖問題。
場景描述:在高并發(fā)系統(tǒng)中吴菠,如在線廣告點擊計數(shù)者填,使用無鎖數(shù)據(jù)結(jié)構(gòu)可以避免鎖的開銷,提高計數(shù)的效率做葵。
import java.util.concurrent.atomic.AtomicInteger;
public class ClickCounter {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
AtomicInteger 類設(shè)計
類設(shè)計圖解釋
-
AtomicInteger:
-
屬性:
-
int value
: 存儲整數(shù)值的變量占哟,通常被標(biāo)記為volatile
以確保內(nèi)存可見性和防止指令重排序。
-
-
方法:
- 構(gòu)造函數(shù) (
AtomicInteger(int initialValue)
): 初始化AtomicInteger
的值酿矢。 -
get()
: 返回當(dāng)前值榨乎。 -
set(int newValue)
: 設(shè)置當(dāng)前值。 -
getAndSet(int newValue)
: 獲取當(dāng)前值瘫筐,并設(shè)置為新值蜜暑。 -
getAndIncrement()
: 獲取當(dāng)前值,并自增策肝。 -
getAndDecrement()
: 獲取當(dāng)前值肛捍,并自減。 -
incrementAndGet()
: 自增之众,并返回新值拙毫。 -
decrementAndGet()
: 自減,并返回新值酝枢。 -
compareAndSet(int expect, int update)
: 如果當(dāng)前值等于預(yù)期值恬偷,則原子地更新為新值悍手。 -
weakCompareAndSet(int expect, int update)
: 與compareAndSet
類似帘睦,但可能不具有相同的內(nèi)存語義。 -
getAndAdd(int delta)
: 獲取當(dāng)前值坦康,并添加指定的增量竣付。 -
addAndGet(int delta)
: 添加指定的增量,并返回新值滞欠。
- 構(gòu)造函數(shù) (
-
-
AtomicReference:
這是一個假設(shè)的內(nèi)部類或輔助類古胆,用于展示
AtomicInteger
可能使用的底層原子引用實現(xiàn)。-
屬性:
-
int value
: 存儲整數(shù)值筛璧。
-
-
方法:
- 構(gòu)造函數(shù) (
AtomicReference(int initialValue)
): 初始化引用的值逸绎。 -
get()
: 返回當(dāng)前引用的值。 -
compareAndSet(int expect, int update)
: 如果當(dāng)前引用的值等于預(yù)期值夭谤,則原子地更新為新值棺牧。 -
set(int newValue)
: 設(shè)置引用的值。
- 構(gòu)造函數(shù) (
Lock-Free設(shè)計思路
核心思路
-
避免阻塞:
- 無鎖數(shù)據(jù)結(jié)構(gòu)通過非阻塞算法來避免線程因等待鎖而進(jìn)入阻塞狀態(tài)朗儒。
-
原子操作:
- 利用原子操作(如CAS - Compare-And-Swap)來確保數(shù)據(jù)項的更新是不可分割的颊乘。
-
避免死鎖:
- 由于不使用鎖参淹,無鎖數(shù)據(jù)結(jié)構(gòu)自然避免了死鎖的可能性。
-
順序保證:
- 通過內(nèi)存模型和原子操作來保證操作的順序性乏悄,確保數(shù)據(jù)的一致性浙值。
-
避免饑餓:
- 設(shè)計算法以確保所有線程最終都能完成其操作,避免某些線程被無限期地延遲檩小。
-
利用多核優(yōu)勢:
- 無鎖數(shù)據(jù)結(jié)構(gòu)可以更好地利用多核處理器的并行處理能力开呐。
實現(xiàn)機制
-
原子變量:
- 使用原子變量(如
AtomicInteger
、AtomicReference
等)來存儲數(shù)據(jù)項规求,這些變量提供了原子操作负蚊。
- 使用原子變量(如
-
比較并交換(CAS) :
- CAS操作是無鎖數(shù)據(jù)結(jié)構(gòu)的核心,它包括三個參數(shù):內(nèi)存位置(V)颓哮、預(yù)期原值(A)和新值(B)家妆。如果內(nèi)存位置的值與預(yù)期原值相等,則將該位置值更新為新值冕茅。
-
失敗重試:
- 當(dāng)CAS操作失敗時(通常是因為其他線程已經(jīng)更改了數(shù)據(jù))伤极,線程會重試操作,直到成功為止姨伤。
-
內(nèi)存屏障:
- 使用內(nèi)存屏障來確保在多處理器系統(tǒng)中內(nèi)存操作的順序性和可見性哨坪。
-
避免ABA問題:
- 使用版本號或標(biāo)記來解決CAS操作可能遇到的ABA問題,即在檢查和更新之間值被改變?nèi)缓笥肿兓卦档那闆r乍楚。
-
偽共享:
- 避免在多核處理器上因緩存行共享導(dǎo)致的性能問題当编,通過調(diào)整數(shù)據(jù)結(jié)構(gòu)的內(nèi)存布局來減少偽共享。
-
無鎖算法設(shè)計:
- 設(shè)計算法時徒溪,需要考慮所有可能的執(zhí)行路徑和線程間的交互忿偷,確保算法的正確性和性能。
-
測試和驗證:
- 無鎖數(shù)據(jù)結(jié)構(gòu)需要通過嚴(yán)格的測試和驗證來確保其在各種并發(fā)條件下的正確性和性能臊泌。
4.4. 樂觀鎖(Optimistic Locking)
場景描述:在分布式系統(tǒng)中鲤桥,配置信息需要被多個服務(wù)共享和更新。使用樂觀鎖可以減少鎖的競爭渠概,提高配置更新的效率茶凳。
public class DistributedConfig {
private String config;
private long version;
public boolean updateConfig(String newConfig) {
long currentVersion = version;
if (version == currentVersion) {
config = newConfig;
version++;
return true;
}
return false;
}
public String getConfig() {
return config;
}
public long getVersion() {
return version;
}
}
4.5. 條件變量(Condition Variables)
場景描述:在任務(wù)調(diào)度系統(tǒng)中,任務(wù)可能需要等待某些條件滿足后才能執(zhí)行播揪。使用條件變量可以有效地管理這些條件的等待和通知贮喧。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.List;
import java.util.ArrayList;
public class TaskScheduler {
private final List<Runnable> tasks = new ArrayList<>();
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void addTask(Runnable task) {
lock.lock();
try {
tasks.add(task);
condition.signalAll();
} finally {
lock.unlock();
}
}
public void executeTasks() {
lock.lock();
try {
while (tasks.isEmpty()) {
condition.await();
}
Runnable task = tasks.remove(0);
task.run();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
}
ReentrantLock 類設(shè)計
核心思路
-
可重入性:
- 允許同一線程多次獲得同一鎖,每次獲得鎖都需要對應(yīng)釋放一次猪狈。
-
公平性選擇:
- 可以選擇公平鎖或非公平鎖箱沦。公平鎖按照線程請求鎖的順序來分配,而非公平鎖則可能允許“插隊”罪裹。
-
鎖的靈活性:
- 提供了嘗試獲取鎖(tryLock)饱普、定時獲取鎖(tryLock with timeout)运挫、可中斷獲取鎖(lockInterruptibly)等操作。
-
條件變量支持:
- 可以與
Condition
接口配合使用套耕,提供更復(fù)雜的線程間協(xié)調(diào)功能谁帕。
- 可以與
實現(xiàn)機制
-
同步器 Sync:
-
ReentrantLock
使用一個內(nèi)部的同步器(如AQS
- AbstractQueuedSynchronizer)來管理鎖的獲取和釋放。
-
-
鎖狀態(tài):
- 維護(hù)一個狀態(tài)變量來表示鎖的持有者和鎖的重入次數(shù)冯袍。
-
線程調(diào)度:
- 當(dāng)鎖被占用時匈挖,請求鎖的線程會被放入同步隊列中等待。
-
鎖的獲取與釋放:
- 提供了獲取和釋放鎖的一系列方法康愤,包括重入的邏輯處理儡循。
-
條件對象:
- 可以為
ReentrantLock
創(chuàng)建一個或多個Condition
對象,用于線程間的等待/通知操作征冷。
- 可以為
Condition Lock設(shè)計思路
核心思路
-
線程同步:
- 條件變量用于同步線程間的操作择膝,使得線程可以在某個條件成立之前掛起。
-
等待/通知機制:
- 線程可以在條件不滿足時掛起(等待)检激,并在條件滿足時被喚醒(通知)肴捉。
-
鎖的結(jié)合使用:
- 條件變量通常與互斥鎖(mutex)結(jié)合使用,以確保在等待條件變量時數(shù)據(jù)的一致性叔收。
-
減少忙等待:
- 通過掛起線程而不是忙等待齿穗,條件變量減少了CPU資源的浪費。
-
響應(yīng)性:
- 條件變量提供了一種機制饺律,允許線程在條件改變時迅速響應(yīng)窃页。
實現(xiàn)機制
-
等待隊列:
- 條件變量內(nèi)部通常有一個等待隊列,所有等待該條件的線程都會被放入這個隊列复濒。
-
鎖的獲取與釋放:
- 在調(diào)用條件變量的
wait()
方法前脖卖,線程必須持有相關(guān)的鎖。 - 在線程等待條件變量時芝薇,它會釋放鎖胚嘲,以便其他線程可以訪問共享資源作儿。
- 當(dāng)條件變量的
signal()
或broadcast()
被調(diào)用時洛二,等待的線程會被喚醒,并在重新嘗試獲取鎖后繼續(xù)執(zhí)行攻锰。
- 在調(diào)用條件變量的
-
條件檢查:
- 線程在被喚醒后晾嘶,需要重新檢查條件是否滿足,因為喚醒不一定意味著條件已經(jīng)滿足娶吞。
-
避免虛假喚醒:
- 線程在被喚醒后必須檢查條件垒迂,以避免虛假喚醒,這是由于操作系統(tǒng)調(diào)度或其他原因?qū)е戮€程被喚醒妒蛇,但條件并未滿足机断。
-
支持多個條件:
- 條件變量可以與多個條件結(jié)合使用楷拳,允許線程等待多個條件中的任何一個。