您好,登錄后才能下訂單哦!
小編給大家分享一下JAVA中如何實現AQS,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
AbstractQueuedSynchronizer是JUC的核心框架,其設計非常精妙。 使用了Java的模板方法模式。 首先試圖還原一下其使用場景:
對于排他鎖,在同一時刻,N個線程只有1個線程能獲取到鎖;其他沒有獲取到鎖的線程被掛起放置在隊列中,待獲取鎖的線程釋放鎖后,再喚醒隊列中的線程。
線程的掛起是獲取鎖失敗時調用Unsafe.park()方法;線程的喚醒是由其他線程釋放鎖時調用Unsafe.unpark()實現。
由于獲取鎖,執行鎖內代碼邏輯,釋放鎖整個流程可能只需要耗費幾毫秒,所以很難對鎖的爭用有一個直觀的感受。下面以3個線程來簡單模擬一下排他鎖的機制。
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)){ // 有問題,非線程安全;只作演示使用 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簡單模擬了排他鎖的工作原理。使用ArrayList屏蔽了鏈表多線程環境下鏈表的構造細節, 該代碼實際上在多線程環境中使用是有問題的,發現了么?
通過上面的代碼,能理解到多線程環境下,鏈表為什么能比ArrayList好使。
理解AQS, 其核心在于理解state
和head
, tail
三個變量。換句話說,理解AQS, 只需理解狀態
和鏈表實現的隊列
這兩樣東西。其使用方式就是,如果更新狀態不成功,就把線程掛起,丟到隊列中;其他線程使用完畢后,從隊列中喚醒一個線程執行。 如果排隊的線程數量過多,那么該誰首先獲得鎖就有講究,不能暗箱操作,所以有公平和非公平兩種策略。
越來越能理解 “編程功底,細節是魔鬼”,理解了上面的使用方式,只相當于理解了需求。那么實現上有那些細節呢? 我們通過問答的方式來闡明。
問題1: state
變量為什么要用volatile關鍵詞修飾?
volatile是synchronized的輕量版本,在特定的場景下具備鎖的特點變量更新的值不依賴于當前值
, 比如setState()
方法。 當volatile的場景不滿足時,使用Unsafe.compareAndSwap即可。
問題2: 鏈表是如何保證多線程環境下的鏈式結構?
首先我們看鏈表是一個雙向鏈表,我們看鏈表呈現的幾個狀態:
1. 空鏈表 (未初始化) head -- null tail -- null or (初始化后) head -- Empty Node tail -- Empty Node 2. 只有一個元素的鏈表 head -- Empty Node <-> Thread Node -- tail
也就是說,當鏈表的不為空時, 鏈表中填充者一個占位節點。
學習數據結構,把插入刪除兩個操作弄明白,基本就明白這個數據結構了。我們先看插入操作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; } } } }
首先一個無限循環。 假如這個鏈表沒有初始化,那么這個鏈表會通過循環的結構插入2個節點。 由于多線程環境下, compareAndSet會存在失敗,所以通過循環保證了失敗重試。 為了保證同步,要么依賴鎖,要么通過CPU的cas。 這里是實現同步器,只能依賴cas。 這種編程結構,看AtomicInteger,會特別熟悉。
接下來看鏈表的刪除操作。當線程釋放鎖調用release()
方法時,AQS會按線程進入隊列的順序喚醒地一個符合條件的線程,這就是FIFO的體現。代碼如下:
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
我們先忽略。這樣的話,線程會從阻塞的后面繼續執行,從parkAndCheckInterrupt()
方法中出來。
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
條件是滿足的。如果獲取到鎖,就把當前節點作為鏈表的head節點:setHead(node)
, 原head節點從鏈表中斷開,讓GC回收p.next=null
。 也就是說,鏈表的刪除是從頭開始刪除,以實現FIFO的目標。
到這里,AQS的鏈表操作就弄清楚了。
以上是“JAVA中如何實現AQS”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。