您好,登錄后才能下訂單哦!
小編給大家分享一下JAVA中如何實(shí)現(xiàn)AQS,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
AbstractQueuedSynchronizer是JUC的核心框架,其設(shè)計(jì)非常精妙。 使用了Java的模板方法模式。 首先試圖還原一下其使用場(chǎng)景:
對(duì)于排他鎖,在同一時(shí)刻,N個(gè)線(xiàn)程只有1個(gè)線(xiàn)程能獲取到鎖;其他沒(méi)有獲取到鎖的線(xiàn)程被掛起放置在隊(duì)列中,待獲取鎖的線(xiàn)程釋放鎖后,再喚醒隊(duì)列中的線(xiàn)程。
線(xiàn)程的掛起是獲取鎖失敗時(shí)調(diào)用Unsafe.park()方法;線(xiàn)程的喚醒是由其他線(xiàn)程釋放鎖時(shí)調(diào)用Unsafe.unpark()實(shí)現(xiàn)。
由于獲取鎖,執(zhí)行鎖內(nèi)代碼邏輯,釋放鎖整個(gè)流程可能只需要耗費(fèi)幾毫秒,所以很難對(duì)鎖的爭(zhēng)用有一個(gè)直觀的感受。下面以3個(gè)線(xiàn)程來(lái)簡(jiǎn)單模擬一下排他鎖的機(jī)制。
import sun.misc.Unsafe; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.LockSupport; public class AQSDemo { private static final Unsafe unsafe = getUnsafe(); private static final long stateOffset; private static Unsafe getUnsafe() { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); return (Unsafe)field.get(null); } catch (Exception e) { } return null; } static{ try{ stateOffset = unsafe.objectFieldOffset (AQSDemo.class.getDeclaredField("state")); } catch (Exception ex) { throw new Error(ex); } } private volatile int state; private List<Thread> threads = new ArrayList<>(); public void lock(){ if(!unsafe.compareAndSwapInt(state,stateOffset,0,1)){ // 有問(wèn)題,非線(xiàn)程安全;只作演示使用 threads.add(Thread.currentThread()); LockSupport.park(); Thread.interrupted(); } } public void unlock(){ state = 0; if(!threads.isEmpty()){ Thread first = threads.remove(0); LockSupport.unpark(first); } } static class MyThread extends Thread{ private AQSDemo lock; public MyThread(AQSDemo lock){ this.lock = lock; } public void run(){ try{ lock.lock(); System.out.println("run "); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } public static void main(String[] args) { AQSDemo lock = new AQSDemo(); MyThread a1 = new MyThread(lock); MyThread a2 = new MyThread(lock); MyThread a3 = new MyThread(lock); a1.start(); a2.start(); a3.start(); } }
上面的代碼,使用park和unpark簡(jiǎn)單模擬了排他鎖的工作原理。使用ArrayList屏蔽了鏈表多線(xiàn)程環(huán)境下鏈表的構(gòu)造細(xì)節(jié), 該代碼實(shí)際上在多線(xiàn)程環(huán)境中使用是有問(wèn)題的,發(fā)現(xiàn)了么?
通過(guò)上面的代碼,能理解到多線(xiàn)程環(huán)境下,鏈表為什么能比ArrayList好使。
理解AQS, 其核心在于理解state
和head
, tail
三個(gè)變量。換句話(huà)說(shuō),理解AQS, 只需理解狀態(tài)
和鏈表實(shí)現(xiàn)的隊(duì)列
這兩樣?xùn)|西。其使用方式就是,如果更新?tīng)顟B(tài)不成功,就把線(xiàn)程掛起,丟到隊(duì)列中;其他線(xiàn)程使用完畢后,從隊(duì)列中喚醒一個(gè)線(xiàn)程執(zhí)行。 如果排隊(duì)的線(xiàn)程數(shù)量過(guò)多,那么該誰(shuí)首先獲得鎖就有講究,不能暗箱操作,所以有公平和非公平兩種策略。
越來(lái)越能理解 “編程功底,細(xì)節(jié)是魔鬼”,理解了上面的使用方式,只相當(dāng)于理解了需求。那么實(shí)現(xiàn)上有那些細(xì)節(jié)呢? 我們通過(guò)問(wèn)答的方式來(lái)闡明。
問(wèn)題1: state
變量為什么要用volatile關(guān)鍵詞修飾?
volatile是synchronized的輕量版本,在特定的場(chǎng)景下具備鎖的特點(diǎn)變量更新的值不依賴(lài)于當(dāng)前值
, 比如setState()
方法。 當(dāng)volatile的場(chǎng)景不滿(mǎn)足時(shí),使用Unsafe.compareAndSwap即可。
問(wèn)題2: 鏈表是如何保證多線(xiàn)程環(huán)境下的鏈?zhǔn)浇Y(jié)構(gòu)?
首先我們看鏈表是一個(gè)雙向鏈表,我們看鏈表呈現(xiàn)的幾個(gè)狀態(tài):
1. 空鏈表 (未初始化) head -- null tail -- null or (初始化后) head -- Empty Node tail -- Empty Node 2. 只有一個(gè)元素的鏈表 head -- Empty Node <-> Thread Node -- tail
也就是說(shuō),當(dāng)鏈表的不為空時(shí), 鏈表中填充者一個(gè)占位節(jié)點(diǎn)。
學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu),把插入刪除兩個(gè)操作弄明白,基本就明白這個(gè)數(shù)據(jù)結(jié)構(gòu)了。我們先看插入操作enq()
:
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; } } } }
首先一個(gè)無(wú)限循環(huán)。 假如這個(gè)鏈表沒(méi)有初始化,那么這個(gè)鏈表會(huì)通過(guò)循環(huán)的結(jié)構(gòu)插入2個(gè)節(jié)點(diǎn)。 由于多線(xiàn)程環(huán)境下, compareAndSet會(huì)存在失敗,所以通過(guò)循環(huán)保證了失敗重試。 為了保證同步,要么依賴(lài)鎖,要么通過(guò)CPU的cas。 這里是實(shí)現(xiàn)同步器,只能依賴(lài)cas。 這種編程結(jié)構(gòu),看AtomicInteger,會(huì)特別熟悉。
接下來(lái)看鏈表的刪除操作。當(dāng)線(xiàn)程釋放鎖調(diào)用release()
方法時(shí),AQS會(huì)按線(xiàn)程進(jìn)入隊(duì)列的順序喚醒地一個(gè)符合條件的線(xiàn)程,這就是FIFO的體現(xiàn)。代碼如下:
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
這里unparkSuccessor()
里面的waitStatus
我們先忽略。這樣的話(huà),線(xiàn)程會(huì)從阻塞的后面繼續(xù)執(zhí)行,從parkAndCheckInterrupt()
方法中出來(lái)。
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); } }
由于喚醒的順序是FIFO, 所以通常p==head
條件是滿(mǎn)足的。如果獲取到鎖,就把當(dāng)前節(jié)點(diǎn)作為鏈表的head節(jié)點(diǎn):setHead(node)
, 原h(huán)ead節(jié)點(diǎn)從鏈表中斷開(kāi),讓GC回收p.next=null
。 也就是說(shuō),鏈表的刪除是從頭開(kāi)始刪除,以實(shí)現(xiàn)FIFO的目標(biāo)。
到這里,AQS的鏈表操作就弄清楚了。
以上是“JAVA中如何實(shí)現(xiàn)AQS”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。