Java中的大部分同步類(Lock、Semaphore、ReentrantLock等)都是基於AbstractQueuedSynchronizer(簡稱爲AQS)實現的。AQS是一種提供了原子式管理同步狀態、阻塞和喚醒線程功能以及隊列模型的簡單框架。本文旨在從ReentrantLock詳解AQS原理源碼解析。java
java.util.concurrent.locks.AbstractQueuedSynchronizer類中存在以下數據結構。node
// 鏈表結點 static final class Node {} // head指向的是一個虛擬結點,刷多了算法就知道這樣作的目的是方便對鏈表操做,真正的頭爲head.next private transient volatile Node head; // 尾結點 private transient volatile Node tail; // 同步狀態,用於展現當前臨界資源的獲鎖狀況。 private volatile int state; // 繼承至AbstractOwnableSynchronizer類 // 獨佔模式下當前鎖的擁有者 private transient Thread exclusiveOwnerThread; // 自旋鎖的自旋納秒數,用於提升應用的響應能力 static final long spinForTimeoutThreshold = 1000L; // unsafe類 private static final Unsafe unsafe = Unsafe.getUnsafe(); // 如下字段對應上面字段的在對象中的偏移值,在靜態代碼塊中初始化,其值是相對於在這個類對象中的偏移量 private static final long stateOffset; private static final long headOffset; private static final long tailOffset; private static final long waitStatusOffset; private static final long nextOffset;
在AQS類中內部類Node包含以下數據結構算法
static final class Node { // 共享鎖 static final Node SHARED = new Node(); // 獨佔鎖 static final Node EXCLUSIVE = null; // 0 當一個Node被初始化的時候的默認值 // CANCELLED 爲 1,表示線程獲取鎖的請求已經取消了 // CONDITION 爲 -2,表示節點在等待隊列中,節點線程等待喚醒 // PROPAGATE 爲 -3,當前線程處在SHARED狀況下,該字段纔會使用 // SIGNAL 爲 -1,表示線程已經準備好了,就等資源釋放了 volatile int waitStatus; static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3; // 前驅指針 volatile Node prev; // 後繼指針 volatile Node next; // 該節點表明的線程對象 volatile Thread thread; Node nextWaiter; }
從其數據結構能夠猜想出c#
咱們從AQS的實現類ReentrantLock#lock開始分析其具體的流程。後端
public void lock() { sync.lock(); }
直接調用了Sync類的lock()方法,Sync類在ReentrantLock中有兩個實現類分別是FairSync和NonfairSync,分別對應了公平鎖和非公平鎖。安全
因爲ReentrantLock默認是非公平鎖,咱們從NonfairSync類分析。數據結構
final void lock() { // cas操做嘗試將state字段值修改成1 if (compareAndSetState(0, 1)) // 成功的話就表明已經獲取到鎖,修改獨佔模式下當前鎖的擁有者爲當前線程 setExclusiveOwnerThread(Thread.currentThread()); else // 獲取鎖失敗以後的操做 acquire(1); }
從這能夠肯定咱們以前的猜想框架
如今分析未獲取到鎖以後的流程jvm
public final void acquire(int arg) { if ( // 當前線程嘗試獲取鎖 !tryAcquire(arg) && // acquireQueued會把傳入的結點在隊列中不斷去獲取鎖,直到獲取成功或者再也不須要獲取(中斷)。 acquireQueued( // 在雙向鏈表的尾部建立一個結點,值爲當前線程和傳入的模式 addWaiter(Node.EXCLUSIVE), arg ) ) // TODO selfInterrupt(); }
看不懂,先查找資料瞭解這幾個方法的做用,註釋在代碼中。函數
// 當前線程嘗試獲取鎖 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
// 當前線程嘗試獲取鎖-非公平 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); // 得到當前鎖對象的狀態 int c = getState(); // state爲0表明當前沒有被線程佔用 if (c == 0) { // cas操做嘗試將state字段值修改成請求的數量 if (compareAndSetState(0, acquires)) { // 直接修改當前獨佔模式下鎖的擁有者爲爲當前線程 setExclusiveOwnerThread(current); return true; } } // 若是鎖的佔有者就是當前線程 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); // state值增長相應的請求數。 setState(nextc); return true; } return false; }
ReentrantLock字面意思是可重入鎖
結合nonfairTryAcquire方法邏輯,能夠推斷出state字段在獨佔鎖模式下還表明了鎖的重入次數。
// 在鏈表尾部建立一個結點,值爲當前線程和傳入的模式 private Node addWaiter(Node mode) { // 建立一個結點,值爲當前線程和傳入的模式 Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure // 快速路徑,是爲了方便JIT優化。jvm檢測到熱點代碼,會將其編譯成本地機器碼並以各類手段進行代碼優化。 Node pred = tail; if (pred != null) { // 將新建立的node的前驅指針指向tail。 node.prev = pred; // 將結點修改成隊列的tail時可能會發生數據衝突,用cas操做保證線程安全。 if (compareAndSetTail(pred, node)) { // compareAndSetTail比較的地址,若是相等則將新的地址賦給該字段(而不是在源地址上替換,爲何我會這麼想???) // 因此此處pred引用指向的仍然是源tail的內存地址。將其後繼指針指向新的tail pred.next = node; return node; } } // 隊列爲空或者cas失敗(說明被別的線程已經修改) enq(node); return node; }
這個方法主要做用是在鏈表尾部建立一個結點,返回新建立的結點,其主要流程爲
當隊列爲空或者cas失敗(說明被別的線程已經修改)會執行enq方法兜底。
// 在隊列尾部建立一個結點,值爲當前線程和傳入的模式,當隊列爲空的時候初始化。 private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize // 建立一個空結點設置爲頭,真正的頭爲hdead.next if (compareAndSetHead(new Node())) // 尾等於頭 tail = head; } else { // 這段邏輯跟addWaiter()中快速路徑的邏輯同樣。 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
addWaiter是對enq方法的一層封裝,addWaiter首先嚐試一個快速路徑的在鏈表尾部建立一個結點,失敗的時候迴轉入enq方法兜底,循環在鏈表尾部建立一個節點,直到成功爲止。
這裏有個疑問,爲何要在addWaiter方法中嘗試一次在enq方法中能完成的在鏈表尾部建立一個節點的操做呢?實際上是爲了方便JIT優化。jvm檢測到熱點代碼,會將其編譯成本地機器碼並以各類手段進行代碼優化。瞭解更多1、瞭解更多2。
在鏈表尾插入須要
// acquireQueued會把傳入的結點在隊列中不斷去獲取鎖,直到獲取成功或者再也不須要獲取(中斷)。 final boolean acquireQueued(final Node node, int arg) { // 標記是否成功拿到鎖 boolean failed = true; try { // 標記獲取鎖的過程當中是否中斷過 boolean interrupted = false; // 開始自旋,要麼獲取鎖,要麼中斷 for (;;) { // 得到其前驅節點 final Node p = node.predecessor(); // 若是前驅節點爲head表明如今節點node在隊列有效數據的第一位,就嘗試獲取鎖 if (p == head && tryAcquire(arg)) { // 獲取鎖成功,把當前節點置爲虛節點 setHead(node); p.next = null; // help GC failed = false; return interrupted; } // 若是存在如下狀況就要判斷當前node是否要被阻塞 // 1. p爲頭節點且獲取鎖失敗 2. p不爲頭結點 if (shouldParkAfterFailedAcquire(p, node) && // 阻塞進程 parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) // 取消申請鎖 cancelAcquire(node); } }
// 依賴前驅節點判斷當前線程是否應該被阻塞 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 入參請求鎖的node的前驅節點的狀態 int ws = pred.waitStatus; // 若是前驅節點的狀態爲"表示線程已經準備好了,就等資源釋放了" // 說明前驅節點處於激活狀態,入參node節點須要被阻塞 if (ws == Node.SIGNAL) return true; // 只有CANCELLED狀態對應大於0 if (ws > 0) { do { // 循環向前查找取消狀態節點,把取消節點從隊列中剔除 node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 設置狀態非取消的前驅節點等待狀態爲SIGNAL compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
到如今咱們能夠總結一下ReentrantLock#lock非公平鎖方法的流程
未獲取到鎖的狀況下函數調用流程
描述
// 公平鎖加鎖時判斷等待隊列中是否存在有效節點的方法。 // 返回False,當前線程能夠爭取共享資源; // 返回True,隊列中存在有效節點,當前線程必須加入到等待隊列中。 public final boolean hasQueuedPredecessors() { Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; // 頭不等於尾表明隊列中存在結點返回true // 可是還有一種特例,就是若是如今正在執行enq方法進行隊列初始化,tail = head;語句運行以後 // 此時h == t,返回false,可是隊列中 return h != t && // 從這能夠看出真正的頭結點是head.next,即說明head是一個無實際數據的結點,爲了方便鏈表操做 ((s = h.next) == null // 有效頭結點與當前線程不一樣,返回true必須加入到等待隊列 || s.thread != Thread.currentThread()); }
Java程序最初都是經過解釋器進行解釋執行的,當虛擬機發現某個方法或代碼塊的運行特別頻繁,就會把這些代碼認定爲「熱點代碼」(Hot Spot Code),爲了提升熱點代碼的執行效率,在運行時,虛擬機將會把這些代碼編譯成本地機器碼,並以各類手段儘量地進行代碼優化,運行時完成這個任務的後端編譯器被稱爲即時編譯器。
這裏所說的熱點代碼主要包括兩類
對於這兩種狀況,編譯的目標對象都是整個方法體,而不會是單獨的循環體