您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“JDK里的自旋鎖怎么用”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“JDK里的自旋鎖怎么用”這篇文章吧。
自旋鎖是采用讓當(dāng)前線程不停地的在循環(huán)體內(nèi)執(zhí)行實現(xiàn)的,當(dāng)循環(huán)的條件被其他線程改變時才能進(jìn)入臨界區(qū)。
JDK里面自旋鎖的實現(xiàn)有 SynchronousQueue 和 LinkedTransferQueue。
先說公平鎖,先等待的線程先獲得數(shù)據(jù)。SynchronousQueue的內(nèi)部類TransferQueue實現(xiàn)了公平鎖。
某一時刻 線程A看到內(nèi)存的情況如下: 鏈表,head 和 tail 分別指向鏈?zhǔn)缀玩溛玻⑶揖€程執(zhí)行了ht = tail 。
* * head ht=tail * | | * v v* M -> U -> U -> U*
M 代表match , 表示該節(jié)點已經(jīng)匹配到了數(shù)據(jù), poll 等到了 offer ,或者 offer 等到了 poll 。
U 代表unmatch , 表示該節(jié)點在等待填充數(shù)據(jù)或者數(shù)據(jù)等待poll 。
由于并發(fā)操作,經(jīng)過其他線程的操作使得鏈表變成如下:(此操作是瞬間完成的)
* head ht tail * | | | * v v v * M -> M -> U -> U -> U -> U*
有兩個U節(jié)點添加進(jìn)來, 第二個節(jié)點M匹配到了數(shù)據(jù)。
假設(shè)此時線程A想再往鏈表添加U節(jié)點:
那么需要進(jìn)行那些操作?
1: 發(fā)現(xiàn)ht != tail , 于是需要繼續(xù)循環(huán)
2:在 ht == tail 的情況下需要先 判斷 tail.next 是否為null , 如果不是則繼續(xù)循環(huán)
3:新建一個node,將 tail.next 指向新node。
4:將U賦值給tail 。 (如果只執(zhí)行了3而還沒有執(zhí)行4,就會出現(xiàn)2中的情況,ht=tail,但是ht.next 為空,說明此時有線程執(zhí)行了3操作但是還沒有執(zhí)行4操作,此時只需要繼續(xù)循環(huán)即可)
* head ht=tail* | |* v v* U -> U -> U -> U -> U
/* * head tail * | | * v v * U -> U -> U -> U -> U * */
5: 自旋等待match。
6: 等待結(jié)束有兩種可能結(jié)果: 6.1 超時取消 6.2 成功
成功時: 因此只需將 head 指向node.next . 并且將 node.next 置空。 雖然此時 M -> M 但是只要其它線程正確的執(zhí)行了M.next = null 即可。
/* * head tail * | | * v v * M M M M M M -> M -> U -> U - > U ->U * *//* * head tail * | | * v v * M M M M M M -> M U -> U - > U ->U * */
等待超時:此時U按理也應(yīng)該來到了鏈?zhǔn)?,前面node的也都超時了,但萬一其他的線程沒有獲得cpu呢,就會出現(xiàn)如下的狀況,需要將U前面的幾個node也順便清理掉
/* * head tail * | | * v v * M M M M M U -> U -> U -> U - > U ->U * */
假設(shè)此時線程A是想將U變成M,這個邏輯很簡單,按順序找到一個U,嘗試給U的item賦值。成功結(jié)束,不成功繼續(xù)循環(huán)。
E transfer(E e, boolean timed, long nanos) { QNode s = null; // constructed/reused as needed boolean isData = (e != null); for (;;) { QNode t = tail; QNode h = head; if (t == null || h == null) // saw uninitialized value continue; // spin if (h == t || t.isData == isData) { // empty or same-mode QNode tn = t.next; if (t != tail) // inconsistent read continue; if (tn != null) { // lagging tail advanceTail(t, tn); continue; }if (timed && nanos <= 0) // can't wait return null; if (s == null) s = new QNode(e, isData); if (!t.casNext(null, s)) // failed to link in continue; advanceTail(t, s); // swing tail and wait Object x = awaitFulfill(s, e, timed, nanos); if (x == s) { // wait was cancelled clean(t, s); return null; }if (!s.isOffList()) { // not already unlinked advanceHead(t, s); // unlink if head if (x != null) // and forget fields s.item = s; s.waiter = null; }return (x != null) ? (E)x : e; } else { // complementary-mode QNode m = h.next; // node to fulfill if (t != tail || m == null || h != head)continue; // inconsistent read Object x = m.item; if (isData == (x != null) || // m already fulfilled x == m || // m cancelled !m.casItem(x, e)) { // lost CAS advanceHead(h, m); // dequeue and retry continue; } advanceHead(h, m); // successfully fulfilled LockSupport.unpark(m.waiter); return (x != null) ? (E)x : e; } } }
自旋分析:
Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {/* Same idea as TransferStack.awaitFulfill */ final long deadline = timed ? System.nanoTime() + nanos : 0L; Thread w = Thread.currentThread(); int spins = ((head.next == s) ? (timed ? maxTimedSpins : maxUntimedSpins) : 0); for (;;) {if (w.isInterrupted()) s.tryCancel(e); Object x = s.item; if (x != e)return x; if (timed) { nanos = deadline - System.nanoTime(); if (nanos <= 0L) { s.tryCancel(e); continue; } }if (spins > 0) --spins; else if (s.waiter == null) s.waiter = w; else if (!timed) LockSupport.park(this); else if (nanos > spinForTimeoutThreshold) LockSupport.parkNanos(this, nanos); } }
循環(huán)判斷 node.item 有沒有變化,如果有變化則匹配成功,如果沒有則繼續(xù)循環(huán), 循環(huán)一定的次數(shù)(spins)后還沒有匹配成功則使用 LockSupport.park() 來阻塞線程。這個循環(huán)過程即可稱為自旋。
但是公平鎖存在一個問題:
/* * * head ht=tail * | | * v v * M -> U -> U -> U * * | * | * U變成M,但是此線程并沒有獲得cpu,沒法立即執(zhí)行,于是后面獲得cpu的線程便有了意見,為啥不將數(shù)據(jù)給我,我能立即執(zhí)行。 */
要想解決此問題說來也十分簡單,每個線程只需不停的將自己的node和head進(jìn)行交換(casHead)即可優(yōu)先獲得task。 這個是在 TransferStack中實現(xiàn)的,說起來簡單,但實現(xiàn)起來則困難得多。其實這也是經(jīng)常聽到的搶占式執(zhí)行吧,SynchronousQueue 默認(rèn)使用非公平鎖。
public SynchronousQueue() {this(false);}public SynchronousQueue(boolean fair) {transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();}
以上是“JDK里的自旋鎖怎么用”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。