網站首頁 編程語言 正文
深入理解AQS
- 一,AQS
- 1,ReentrantLock
- 2,CAS
- 3,AbstractQueuedSynchronizer
- 3.1,FairSync
- 3.2,NofairSync
- 3.3,AQS中幾個重要的相關參數
- 3.4,Node
一,AQS
AbstractQueuedSynchronizer,定義了一套多線程訪問共享資源的同步器框架,依賴于狀態的同步器
1,ReentrantLock
一種基于AQS框架的應用實現,類似于synchronized是一種互斥鎖,可以保證線程安全。它支持手動加鎖與解鎖,支持加鎖的公平性。主要是Lock鎖的實現
public class ReentrantLock implements Lock, Serializable
接下來可以手動的猜想一下這個reentrantLock的實現
ReentrantLock lock = new ReentrantLock(true);
HashSet hashSet = new HashSet();
3個線程 T0,T1,T2
lock.lock(); //加鎖
while(true){ //循環,輪詢獲取鎖
if(加鎖成功){ //synchronized,cas cas:compare and swap
break;//跳出循環
}
//Thread.yeild() //讓出cpu使用權
//Thread.sleep(1000); //睡眠
hashSet.add(thread); //將當前線程加入到set中
//阻塞
LockSupprot.park();
}
T0獲取鎖
xxxxx業務邏輯
xxxxx業務邏輯
lock.unlock();
Thread thread = hashSet.get();
//喚醒當前線程,notify是喚醒隨機的線程
LockSupport.unPark(thread);
三大核心:自旋,加鎖,LockSupport,隊列(LinkQueue),為了解決這個公平鎖和非公平鎖,因此優先考慮這個隊列。
2,CAS
compare and swap,比較與交換
如在jmm模型中,兩個工作內存都去修改主內存的值。主內存中存在一個a = 0,線程A和線程B的工作內存同時獲取到這個值,如果線程A先修改這個值,則線程A會和主內存比較,如果線程A的值a和主內存的值一致,那么就會直接進行修改,如改成a = 1,那么線程B也要改這個值,線程B中的a = 0,那么會和主內存a比較,發現不一致,主內存a=1,那么就會優先將線程B中的值修改成a = 1,再對a進行修改。就是說相等直接修改,不相等需要重新讀取,再進行修改。即在一個原子操作里面進行比較和替換
主要通過這個unsafe類實現,里面的實現也是原子類操作,主要是通過以下三個類實現。
//對象類型
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
//整型值
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
//Long型值
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
3,AbstractQueuedSynchronizer
該類是ReentrantLock里面的一個抽象內部類。ctrl + alt + shift + u看所有子類,Ctrl + T,看所有的繼承類,可以發現很多地方都繼承或者實現了這個抽象類
Sync是ReentrantLock的一個抽象的靜態內部類,根據圖也可以發現這個Sync繼承了這個AQS
abstract static class Sync extends AbstractQueuedSynchronizer
通過實現Sync這個接口得到了FairSync公平鎖類和NofairSync非公平鎖這個類
3.1,FairSync
實現了公平鎖,需要排隊獲取鎖,如存在線程t1,t2,t3依次獲取鎖,需要依次排隊執行,突然來了一個線程t4,也是需要排在線程t3后面
ReentrantLock reentrantLock = new ReentrantLock(true);
public ReentrantLock(boolean fair) {
//入參,用于判斷是公平鎖還是非公平鎖
sync = fair ? new FairSync() : new NonfairSync();
}
//公平鎖的實現
static final class FairSync extends ReentrantLock.Sync{...}
公平鎖獲取鎖的方式如下
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
//tryAcquire:嘗試去獲取鎖
//addWaiter:線程入隊,一個同步等待隊列,基于雙向列表實現
//鏈表中的每一個結點為一個Node結點,
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//線程入隊操作
private Node addWaiter(Node mode) {
//獲取當前線程結點
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//結點入隊
enq(node);
return node;
}
//判斷是否獲取鎖
protected final boolean tryAcquire(int acquires) {
//獲取當前獲取鎖的線程
final Thread current = Thread.currentThread();
//獲取當前同步器狀態,被volatile修飾的整型值,默認為0
int c = getState();
//如果同步狀態器的值為0,說明外面的線程可以進行加鎖操作
if (c == 0) {
//hasQueuedPredecessors:判斷隊列中是否還有在排隊的節點
//compareAndSetState:原子操作,比較與交換,進行加鎖的操作,將state變量將0變為1
//setExclusiveOwnerThread:設置獲取鎖的的線程擁有者
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果狀態同步器不為0,可能由自己持有,也可能由別的線程持有鎖
//重復加鎖,如定義一個全局鎖,出現了這個可重入鎖的問題
else if (current == getExclusiveOwnerThread()) {
//如果自己持有鎖的話,state+1即可,反正不等于0就可以
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//別的線程獲有鎖,直接返回
return false;
}
總而言之就是說,公平鎖就是就是通過一個隊列實現,需要進行排隊的去獲取鎖資源。主要是通過這個state的資源狀態器來控制獲取鎖的擁有者,如果state為0,則表示隊列中的下一個線程可以去獲取鎖,并且通過cas的方式來保證鎖的安全并發問題。通過隊列的思想,來保證這個獲取鎖的公平性和有序性。
3.2,NofairSync
實現了非公平鎖,默認為非公平鎖,會存在搶鎖的情況
ReentrantLock reentrantLock = new ReentrantLock(flase);
//如果不傳入參數,默認是非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
//入參,用于判斷是公平鎖還是非公平鎖
sync = fair ? new FairSync() : new NonfairSync();
}
//非公平鎖的實現
static final class NonfairSync extends ReentrantLock.Sync{...}
非公平鎖獲取鎖的方法和公平鎖類似,只是少了幾步使用隊列的幾個方法
3.3,AQS中幾個重要的相關參數
exclusiveOwnerThread:用于記錄當前獨占模式下,獲取鎖的線程是誰
state:同步狀態器,默認為0,表示當前沒有線程獲取鎖,外面的線程可以來獲取鎖了
Node:雙向鏈表結構,是一個同步等待隊列,head:隊頭,tail:隊尾,prev前軀指針,next,后繼指針
waiteState:結點的什生命狀態
Lock鎖和synchronized鎖都是可重入鎖
3.4,Node
pre:前驅指針
next:后繼指針
waitStatues:每個結點都存在很多狀態,這個主要是存儲結點的生命狀態
結點的幾個生命狀態如下
SIGNAL:-1 //可被喚醒
CANCELLED:1 //代表異常,中斷引起的,需要被廢棄
CONDITION:-2 //條件等待
PROPAGATE:-3 //傳播
Init:0初始狀態
結點入隊順序如下,入隊時,將入隊結點得前驅指針指向鏈表的tail結點,將tail節點的next節點指向當前節點,并將當前結點設置為tail尾指針結點。
private Node enq(final Node node) {
//自旋
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//如果隊列為空,要防止出現多個線程的并發問題,結點直接放在隊頭
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
//保證這個入隊的安全性,防止入隊出現并發問題
//結點入隊到隊尾
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
當前結點前面有結點獲取鎖,當前節點需要進行阻塞park,線程要開始排隊等待。
結點在阻塞之前,還得嘗試獲取一次鎖。
a,如果結點可以獲取到鎖,即當前節點為頭結點的下一個結點,頭結點即將鎖被釋放,則把當前結點作為頭結點。之前的頭結點就可以被GC回收了
b,如果結點不能獲取到鎖,那么當前結點就要等待被喚醒
? (1),第一輪循環會去修改head狀態,并且將waitState修改為sinal = -1可被喚醒狀態
? (2),第二輪,阻塞線程
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//當前節點的前驅指針指向的節點
final Node p = node.predecessor();
//如果當前結點指向的該結點為頭部結點,則不需要進行阻塞
if (p == head && tryAcquire(arg)) {
//將當前結點作為頭部結點
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
每個結點的生命狀態的變換如下。默認的結點狀態為0,需要將節點狀態(waitState)轉化為-1可喚醒狀態。前一個節點中的waitStatus狀態記錄著后一個節點的生命狀態。
//pred:前驅節點 node:當前節點
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//默認為初始狀態0
int ws = pred.waitStatus;
//SIGNAL為-1,可被喚醒狀態
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//將頭結點初始狀態轉化為可喚醒狀態
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
在修改成可被喚醒的狀態之后,就進行阻塞操作。
private final boolean parkAndCheckInterrupt() {
//阻塞線程,并且判斷該線程是否是被中斷的
LockSupport.park(this);
return Thread.interrupted();
}
在頭結點釋放鎖的時候,也會發一個通知去告知下一個需要獲取鎖的線程來搶鎖,即喚醒隊列中的下一個被阻塞的線程
//真正的釋放鎖
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//嘗試釋放鎖
if (tryRelease(arg)) {
Node h = head;
//因為前一個結點中存放后一個節點中的聲明狀態,因此
//如果頭結點的waitStatus不為0,就說明后一個結點是一個可被喚醒的狀態
if (h != null && h.waitStatus != 0)
//喚醒
unparkSuccessor(h);
return true;
}
return false;
}
//嘗試去釋放鎖
protected final boolean tryRelease(int releases) {
//修改同步狀態器state
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//將同步狀態器state 變為 0,說明當前同步狀態器可以進行鎖的獲取了
if (c == 0) {
free = true;
//置空當前線程
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
最后在這個unparkSuccessor方法中,也有這個具體的unpark喚醒操作
if (s != null)
LockSupport.unpark(s.thread)
通過上述代碼描述,也驗證了一開始的猜想:自旋,加鎖,LockSupport,隊列(LinkQueue)
?
原文鏈接:https://blog.csdn.net/zhenghuishengq/article/details/125648495
相關推薦
- 2022-07-30 使用?gomonkey?Mock?函數及方法示例詳解_Golang
- 2022-11-10 android時間選擇控件之TimePickerView使用方法詳解_Android
- 2022-07-31 C語言算法積累加tag的循環隊列_C 語言
- 2022-02-11 小程序如何把參數設置為全局變量
- 2022-05-15 C++中類的轉換函數你了解嗎_C 語言
- 2022-07-08 Python實現超快窗口截圖功能詳解_python
- 2022-08-13 oracle利用sql語句實現分組小計(grouping,group by ,rollup)
- 2023-07-03 Python實現曲線的肘部點檢測詳解_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支