(1)AQS是什麼?java
(2)AQS的定位?多線程
(3)AQS的實現原理?框架
(4)基於AQS實現本身的鎖?ide
AQS的全稱是AbstractQueuedSynchronizer,它的定位是爲Java中幾乎全部的鎖和同步器提供一個基礎框架。學習
AQS是基於FIFO的隊列實現的,而且內部維護了一個狀態變量state,經過原子更新這個狀態變量state便可以實現加鎖解鎖操做。ui
本章及後續章節的內容理解起來可能會比較晦澀,建議先閱讀彤哥上一章的內容【死磕 java同步系列之本身動手寫一個鎖Lock】。this
static final class Node { // 標識一個節點是共享模式 static final Node SHARED = new Node(); // 標識一個節點是互斥模式 static final Node EXCLUSIVE = null; // 標識線程已取消 static final int CANCELLED = 1; // 標識後繼節點須要喚醒 static final int SIGNAL = -1; // 標識線程等待在一個條件上 static final int CONDITION = -2; // 標識後面的共享鎖須要無條件的傳播(共享鎖須要連續喚醒讀的線程) static final int PROPAGATE = -3; // 當前節點保存的線程對應的等待狀態 volatile int waitStatus; // 前一個節點 volatile Node prev; // 後一個節點 volatile Node next; // 當前節點保存的線程 volatile Thread thread; // 下一個等待在條件上的節點(Condition鎖時使用) Node nextWaiter; // 是不是共享模式 final boolean isShared() { return nextWaiter == SHARED; } // 獲取前一個節點 final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; } // 節點的構造方法 Node() { // Used to establish initial head or SHARED marker } // 節點的構造方法 Node(Thread thread, Node mode) { // Used by addWaiter // 把共享模式仍是互斥模式存儲到nextWaiter這個字段裏面了 this.nextWaiter = mode; this.thread = thread; } // 節點的構造方法 Node(Thread thread, int waitStatus) { // Used by Condition // 等待的狀態,在Condition中使用 this.waitStatus = waitStatus; this.thread = thread; } }
典型的雙鏈表結構,節點中保存着當前線程、前一個節點、後一個節點以及線程的狀態等信息。線程
// 隊列的頭節點 private transient volatile Node head; // 隊列的尾節點 private transient volatile Node tail; // 控制加鎖解鎖的狀態變量 private volatile int state;
定義了一個狀態變量和一個隊列,狀態變量用來控制加鎖解鎖,隊列用來放置等待的線程。code
注意,這幾個變量都要使用volatile關鍵字來修飾,由於是在多線程環境下操做,要保證它們的值修改以後對其它線程當即可見。隊列
這幾個變量的修改是直接使用的Unsafe這個類來操做的:
// 獲取Unsafe類的實例,注意這種方式僅限於jdk本身使用,普通用戶是沒法這樣調用的 private static final Unsafe unsafe = Unsafe.getUnsafe(); // 狀態變量state的偏移量 private static final long stateOffset; // 頭節點的偏移量 private static final long headOffset; // 尾節點的偏移量 private static final long tailOffset; // 等待狀態的偏移量(Node的屬性) private static final long waitStatusOffset; // 下一個節點的偏移量(Node的屬性) private static final long nextOffset; static { try { // 獲取state的偏移量 stateOffset = unsafe.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("state")); // 獲取head的偏移量 headOffset = unsafe.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("head")); // 獲取tail的偏移量 tailOffset = unsafe.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("tail")); // 獲取waitStatus的偏移量 waitStatusOffset = unsafe.objectFieldOffset (Node.class.getDeclaredField("waitStatus")); // 獲取next的偏移量 nextOffset = unsafe.objectFieldOffset (Node.class.getDeclaredField("next")); } catch (Exception ex) { throw new Error(ex); } } // 調用Unsafe的方法原子更新state protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
關於Unsafe類的講解請參考彤哥以前寫的【死磕 java魔法類之Unsafe解析】。
咱們能夠看到AQS的全稱是AbstractQueuedSynchronizer,它本質上是一個抽象類,說明它本質上應該是須要子類來實現的,那麼子類實現一個同步器須要實現哪些方法呢?
// 互斥模式下使用:嘗試獲取鎖 protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } // 互斥模式下使用:嘗試釋放鎖 protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); } // 共享模式下使用:嘗試獲取鎖 protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); } // 共享模式下使用:嘗試釋放鎖 protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException(); } // 若是當前線程獨佔着鎖,返回true protected boolean isHeldExclusively() { throw new UnsupportedOperationException(); }
問題:這幾個方法爲何不直接定義成抽象方法呢?
由於子類只要實現這幾個方法中的一部分就能夠實現一個同步器了,因此不須要定義成抽象方法。
下面咱們經過一個案例來介紹AQS中的部分方法。
直接上代碼:
public class MyLockBaseOnAqs { // 定義一個同步器,實現AQS類 private static class Sync extends AbstractQueuedSynchronizer { // 實現tryAcquire(acquires)方法 @Override public boolean tryAcquire(int acquires) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } // 實現tryRelease(releases)方法 @Override protected boolean tryRelease(int releases) { setExclusiveOwnerThread(null); setState(0); return true; } } // 聲明同步器 private final Sync sync = new Sync(); // 加鎖 public void lock() { sync.acquire(1); } // 解鎖 public void unlock() { sync.release(1); } private static int count = 0; public static void main(String[] args) throws InterruptedException { MyLockBaseOnAqs lock = new MyLockBaseOnAqs(); CountDownLatch countDownLatch = new CountDownLatch(1000); IntStream.range(0, 1000).forEach(i -> new Thread(() -> { lock.lock(); try { IntStream.range(0, 10000).forEach(j -> { count++; }); } finally { lock.unlock(); } // System.out.println(Thread.currentThread().getName()); countDownLatch.countDown(); }, "tt-" + i).start()); countDownLatch.await(); System.out.println(count); } }
運行main()方法老是打印出10000000(一千萬),說明這個鎖也是能夠直接使用的,固然這也是一個不可重入的鎖。
是否是很簡單,只須要簡單地實現AQS的兩個方法就完成了上一章彤哥本身動手實現的鎖的功能。
它是怎麼實現的呢?
咱們這一章先不講源碼,後面學習了ReentrantLock天然就明白了。
這一章就到此結束了,本篇沒有去深刻的解析AQS的源碼,筆者認爲這沒有必要,由於對於歷來都沒有看過鎖相關的源碼的同窗來講,一上來就講AQS的源碼確定會一臉懵逼的,具體的源碼咱們穿插在後面的鎖和同步器的部分來學習,等全部跟AQS相關的源碼學習完畢了,再來一篇總結。
下面總結一下這一章的主要內容:
(1)AQS是Java中幾乎全部鎖和同步器的一個基礎框架,這裏說的是「幾乎」,由於有極個別確實沒有經過AQS來實現;
(2)AQS中維護了一個隊列,這個隊列使用雙鏈表實現,用於保存等待鎖排隊的線程;
(3)AQS中維護了一個狀態變量,控制這個狀態變量就能夠實現加鎖解鎖操做了;
(4)基於AQS本身動手寫一個鎖很是簡單,只須要實現AQS的幾個方法便可。
上一章彤哥本身動手寫的鎖,其實能夠當作是AQS的一個縮影,看懂了那個基本上AQS能夠看懂一半了,由於彤哥那個裏面沒有寫Condition相關的內容,下一章ReentrantLock重入鎖中咱們將一塊兒學習Condition相關的內容。
因此呢,仍是建議你們去看看這篇文章,點擊下面的推薦閱讀能夠直達。
歡迎關注個人公衆號「彤哥讀源碼」,查看更多源碼系列文章, 與彤哥一塊兒暢遊源碼的海洋。