這篇文章給大家分享的是有關(guān)AbstractQueuedSynchronizer預(yù)熱的示例分析的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

成都創(chuàng)新互聯(lián)主營安慶網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,重慶APP開發(fā)公司,安慶h5微信平臺小程序開發(fā)搭建,安慶網(wǎng)站營銷推廣歡迎安慶等地區(qū)企業(yè)咨詢
// 我不確定有多少人卡在這里
// 我是這么理解的 某個對象在jvm當中 是用一塊數(shù)據(jù)來描述對象的所有信息
// 那么問題來了 如果我要設(shè)置某個對象的字段 通常的方法 對象引用.setXXXField(xxx)這個是通常的方法
// 還有一種比較特別的 unsafe提供的 unsafe.objectFieldOffset獲取某個字段的偏移量 可以理解為存儲信息的地址
// 獲得了偏移地址之后 就可以使用 unsafe.compareAndSwapObject來原子的設(shè)置某個對象的字段
// 就是說 繞過通用的流程 直接修改相關(guān)數(shù)據(jù)了 順帶而且是原子性的
// 可以理解為玩游戲用外掛直接修改內(nèi)存這種場景
headOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
unsafe.compareAndSwapObject(this, headOffset, expect, update);
tailOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
unsafe.compareAndSwapObject(this, tailOffset, expect, update);
/**
* 獨占式獲取同步狀態(tài),忽略線程的打斷。
* 獲取同步狀態(tài)的邏輯是由重寫的模板方法tryAcquire來實現(xiàn)的。
* 如果獲取同步狀態(tài)成功,則方法就直接返回。
* 否則,線程就會入隊,一直會處于阻塞或者自旋,直到重復(fù)嘗試tryAcquire成功。
* 該方法就是接口Lock#lock的實現(xiàn)。
* (從方法的介紹上面理解,就是說,這個接口直接的效果就是,獲取同步成功,線程就從這個方法繼續(xù)執(zhí)行下去,如果不成功;
* 那么內(nèi)部會經(jīng)過一系列復(fù)雜的邏輯計算,直接體現(xiàn)就是線程不會繼續(xù)執(zhí)行下去,就一直處于這個方法內(nèi)部。不執(zhí)行下去的原因是:線程可能處于自旋或者阻塞。)
* @param arg 同步狀態(tài)參數(shù) 透傳進tryAcquire并且不響應(yīng)終端或者其他情況(超時)
*
* 由兩種判斷邏輯
* 1. tryAcquire(arg) -> 返回
* 2. tryAcquire(arg) -> addWaiter(Node.EXECLUSIVE) -> acquireQueued(lastValue, arg) -> 返回并且可能會中斷線程
*
* addWaiter(Node node) 入隊
* acquireQueued(final Node node, int arg) 自旋或者阻塞
*
* 這個方法就是把整個流程已經(jīng)寫死了,必定會經(jīng)過這么幾個步驟。
* 唯一可以影響該方法中的流程,只能是模板方法tryAcquire,它的返回與否,導(dǎo)致流程的走向。
* 把自旋或者阻塞安排在if的條件語句中 會令人初步一看會感覺非常難受。(大神可以這么用,我們平時還是少用)。
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 老老實實的我,一般會這么寫 見笑見笑
public final void acquire(int arg) {
// 嘗試獲取同步狀態(tài)
if (tryAcquire(arg)) {
return;
} else {
// 先入隊 這里會有一個死循環(huán)
Node newNode = addWaiter(Node.EXCLUSIVE);
// 再自旋獲取同步狀態(tài) 或者阻塞 這里也會有死循環(huán)
boolean shouldCurrentThreadInterrupted = acquireQueued(newNode, arg);
// 再判斷是否需要線程中斷
if (shouldCurrentThreadInterrupted) {
selfInterrupt();
}
}
}
// 接下來看看這個模板方法的介紹
/**
* 嘗試獨占式獲取同步狀態(tài)。
* 該方法需要查詢對象當前狀態(tài),判斷同步狀態(tài)是否符合預(yù)期。
* (我的理解就是,需要自己實現(xiàn)自己的邏輯,判斷自己所要實現(xiàn)的邏輯是否符合自己的預(yù)期。記住是獨占模式)
*
* 該方法經(jīng)常再線程執(zhí)行同步時被調(diào)用。
* 如果方法返回失敗,那么線程就應(yīng)該入隊了,即使線程還沒做好入隊準備。
* (這里的意思就是說,線程在競爭鎖之前,最好做好充足的準備工作,也就是前置邏輯要執(zhí)行完,比如各種初始化判斷。加鎖之后就應(yīng)該是確確實實的邏輯操作了,最好不要加完鎖之后,又去判斷各種前置業(yè)務(wù)邏輯操作。這個就是我理解的大師所要闡述的最佳實踐。)
* 入隊的線程只能等待別人釋放之后喚醒。
* 一般前置方法就是為了實現(xiàn)Lock#tryLock這個。
*
* 默認實現(xiàn)式UnsupportedOperationException異常。
*
* @param arg 請求參數(shù)。
* 一般這個值是方法唯一的參數(shù),或者保存于條件等待中。
* 所以不建議為這個值賦予更多其他含義。
* (我認為這里的意思是,這個值不要和業(yè)務(wù)中的某個條件或者流程掛鉤,讓值單純的標識同步狀態(tài)就好了。)
*
* @return true加鎖成功。
* @throws IllegalMonitorStateException 如果獲取同步時發(fā)現(xiàn)同步器處于一個不正確的狀態(tài)時,
* 那么就必須拋出這個異常,目的時為了同步器邏輯正確。
* (我的理解,同步器狀態(tài)很重要,必須嚴肅對待,因為一旦某個過程狀態(tài)不正確,后續(xù)的業(yè)務(wù)邏輯可能會發(fā)生各種不可知的結(jié)果,并且,debug起來非常麻煩,因為業(yè)務(wù)邏輯可能正確,原因是同步狀態(tài)的出錯。這種是很隱晦的。也就是說,一旦碰到IllegalMonitorStateException,個人認為最好中斷運行,排錯。即使開發(fā)者認為這個錯誤不重要。你都已經(jīng)自己實現(xiàn)鎖的邏輯了,任何一點小的邏輯失誤,都會造成不可預(yù)估的結(jié)果。千里之堤毀于蟻穴啊。)
* @throws UnsupportedOperationException 如果獨占模式不支持拋異常
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// 入隊操作
/**
* 創(chuàng)建隊列,并且把當前線程包裝一下,指定某個節(jié)點模式,入隊。
*
* @param mode Node.EXCLUSIVE 獨占, Node.SHARED 共享
* @return 新的節(jié)點
*/
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;
// 隊尾有兩種情況
// 1 null 表示隊列還沒有初始化 初始化在enq(node)中
// 2 != null 表示隊列初始化了 那么嘗試快速添加隊尾這個操作 我認為就是優(yōu)化操作了
// (老老實實的我,一般并不會這么寫,因為我比較穩(wěn)妥。)
// (其實優(yōu)化操作,理論上來說,可以不用的。)
// compareAndSetTail()這個原子性的操作 防止并發(fā)
// 并發(fā)操作的特點就是,隨時隨地都可能發(fā)生幾個線程同時執(zhí)行,所以,并發(fā)點,盡量條件簡單點,如果業(yè)務(wù)條件夠復(fù)雜,一定要拆,而且要分優(yōu)先級的。不然,動態(tài)變化的條件加上鎖,噩夢。
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
// 入隊操作只需要建立一個尾鏈接就可以
pred.next = node;
return node; // 注意 這里返回的是新的節(jié)點
}
}
enq(node); // 這里方法返回的是節(jié)點前置的節(jié)點 但是沒有使用 在喚醒流程中會復(fù)用這個方法
return node;
}
// 完整的入隊流程邏輯
/**
* 入隊操作,一定要先初始化隊列。
* (死循環(huán)確保一定會入隊成功,我對死循環(huán)的理解是,單線程不要用死循環(huán),多線程可以適量的用,主線程不要用,非要用時情愿開個線程計算,等它計算結(jié)束再拿那個結(jié)果也可以。總結(jié)起來,能不用就不用,即使要用,千萬別忘記了,自己在干什么。建議在自己精力最旺盛的時候,寫帶有死循環(huán)的邏輯。)
* @param node 入隊節(jié)點
* @return 返回前置節(jié)點
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 隊列初始化
// 原子性的設(shè)置頭 這里注意這個head節(jié)點 這個head指向的node是一個空的node,里面沒有node的關(guān)鍵數(shù)據(jù)的
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 雙向隊列 嘗試把當前節(jié)點的頭設(shè)置為原本隊尾那個 只要下面的cas隊列設(shè)置好那就操作成功 不行再循環(huán)再來
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
/**
* 設(shè)置隊列首節(jié)點 (因為是雙向,隊首的前驅(qū)是null,這個null是為了釋放節(jié)點的。)
* 該方法僅僅只被同步器獲取。
* null的目的是為了GC也為了不必要的信號釋放遍歷。
*
* @param node 設(shè)置隊首
*/
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
// 自旋
/**
* 獨占不響應(yīng)中斷模式的線程獲取同步方法。
* 條件等待也使用該方法。
*
* @param node 節(jié)點
* @param arg 獲取同步參數(shù)
* @return true 如果等待時線程被打斷
*/
final boolean acquireQueued(final Node node, int arg) {
// 獲取同步狀態(tài)是否失敗
// 默認標記值是成功的
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 節(jié)點的前驅(qū)節(jié)點就是頭節(jié)點
// 說明前面的節(jié)點,要么持有同步狀態(tài)在進行業(yè)務(wù)邏輯操作,要么就已經(jīng)釋放鎖了。這種情況下,獲取同步器機會就很大。
// 再次嘗試獲取同步狀態(tài)
if (p == head && tryAcquire(arg)) {
// 這里已經(jīng)說明當前節(jié)點已經(jīng)獲得了同步狀態(tài) 也就是說當前線程也獲得執(zhí)行業(yè)務(wù)邏輯的機會了
// 設(shè)置頭節(jié)點很有技巧 設(shè)置完之后 頭已經(jīng)是一個虛擬的節(jié)點了
setHead(node);
p.next = null; // help GC
failed = false; // 這里其實個人認為是不需要設(shè)置了 除了習(xí)慣原因 我不知道還有什么特別的意思?因為返回的時候是表示線程是否被打斷了標記
return interrupted;
}
// 獲取失敗判斷線程是否需要阻塞
// 阻塞之后又要檢查線程是否需要中斷
//
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true; // 線程已經(jīng)被打斷
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* 當一個節(jié)點獲取同狀態(tài)失敗時,檢查并且更新它的狀態(tài)。
* 返回true,那么線程需要被阻塞。
* 在所有的獲取同步循環(huán)中,這個是最重要的信號控制。
* 前置條件是前置節(jié)點確切的是節(jié)點的前置節(jié)點。
*
* @param pred 帶有狀態(tài)的前驅(qū)節(jié)點
* @param node 節(jié)點
* @return true 線程被阻塞
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* 前驅(qū)節(jié)點已經(jīng)處于等待其他線程釋放同步狀態(tài)而將它喚醒。
* 那么當前節(jié)點應(yīng)該能夠安全的被阻塞。
*/
return true;
if (ws > 0) {
/*
* 前驅(qū)節(jié)點已經(jīng)是取消狀態(tài)。
* 跳過前驅(qū)節(jié)點在嘗試。
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* 等待狀態(tài)必須是0或者是傳播狀態(tài)(-3)。
* 僅需要一個信號,而并不需要阻塞。(應(yīng)該是共享模式下的邏輯。)
* 調(diào)用者需要重新確保當前線程在阻塞之前是否需要獲取同步狀態(tài)。
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
/**
* 阻塞當前線程。恢復(fù)后檢測線程是否被中斷了。
*
* @return true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}感謝各位的閱讀!關(guān)于“AbstractQueuedSynchronizer預(yù)熱的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
網(wǎng)站題目:AbstractQueuedSynchronizer預(yù)熱的示例分析
標題路徑:http://chinadenli.net/article16/gphcdg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制開發(fā)、網(wǎng)站維護、網(wǎng)站制作、標簽優(yōu)化、定制網(wǎng)站、微信公眾號
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)