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