寫這篇文章以前,仍是先安利一本書:《java併發編程的藝術》。這本書對鎖的實現的不少細節都解釋的仍是很清楚的,加上本身配合源碼進行理解,讀懂ReentrantLock這個類的實現應該不是那麼困難。本文只對獨佔模式進行分析。java
直接步入正題,先貼一段代碼看看如何使用ReentrantLock:node
public class ReentrantLockTest { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(true); //1 lock.lock(); //2 try { //do something } finally { lock.unlock(); //3 } } }
上面代碼的步驟1是調用ReentrantLock構造方法進行初始化,這裏ReentrantLock給咱們提供了兩種鎖的實現,一個是公平鎖,一個是非公平鎖。這兩種鎖顧名思義,一個排隊幹活,一個搶着幹~~編程
//默認構造函數,獲得的是非公平鎖的實現 public ReentrantLock() { sync = new NonfairSync(); } //傳入true獲得公平鎖的實現,傳入false則獲得公平鎖的實現 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
ReentrantLock鎖的使用的入口在lock方法,下面我們針對公平鎖lock方法的實現進行分析一波(能看懂這個相信對非公平鎖的lock的實現的理解也就不會有什麼難度了)。併發
這裏我把全部的方法都放在一塊兒,方便你們閱讀:函數
//這裏在併發狀況下會有競爭 final void lock() { acquire(1); } //來至於父類AQS中 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } //公平鎖自身提供的實現方法,來保證鎖的獲取是按照FIFO原則.也就是隊列模型,先入先出。 protected final boolean tryAcquire(int acquires) { //獲取當前線程 final Thread current = Thread.currentThread(); //拿到鎖標記的狀態值,爲0則表明這把鎖沒人佔用 int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { //將幹活的人的身份標記一下 setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { //這裏是重入鎖的關鍵代碼,只要是獲取鎖的線程再次去拿這把鎖,則能夠直接獲取成功, //並將state的值+1後從新設置,供後面釋放鎖的時候進行屢次釋放使用。 int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); //這裏有個優雅的小細節:我們發現設置狀態時並無使用compareAndSetState這種方法, //而是直接設置。那是由於在這種條件下不會有競爭,只多是獲取鎖的線程才能去改變這個值。 setState(nextc); return true; } return false; } //用來判斷是否在它以前已經有人排在隊列當中了,若是有,則返回true public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; //這裏返回時的判斷條件可能有點難理解。假設當前是A線程。 //1.第一種狀況發生在有一個B線程進度比A快,已經準備開始排隊了。能夠看下面addWaiter方法 //的調用,在進行compareAndSetTail交換後,有可能還沒來得及將pred.next指向這個新節點node, //這個時候說明已經有人在A線程前面去排隊拿鎖了。 //2.第二種狀況簡單明瞭。A線程不是排在隊列的第一個的,也證實了有人排在他前面了。 return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); } //用來添加新的節點到隊列的尾部。 private Node addWaiter(Node mode) { //根據傳進來的參數mode=Node.EXCLUSIVE,表示將要構造一個獨佔鎖。 Node node = new Node(Thread.currentThread(), mode); Node pred = tail; //tail爲空的狀況下直接調用enq方法去進行head和tail的初始化。 if (pred != null) { //tail不爲空的狀況下,將新構造節點的前驅設置爲原尾部節點。 node.prev = pred; //使用CAS進行交換,若是成功,則將原尾部節點的後繼節點設置爲新節點,作雙向列表關聯; //(這裏要注意一點,交換成功的同時有其餘線程讀取該列表,有可能讀取不到新節點。例如A線程 //執行完下方步驟1後,還未執行步驟2,遍歷的時候將會獲取不到新節點,這也是 //hasQueuedPredecessors方法中的第一種狀況) //若是不成功,則表明有競爭,有其餘線程修改了尾部,則去調用下方enq方法 if (compareAndSetTail(pred, node)) { //1 pred.next = node; //2 return node; } } enq(node); return node; } private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize //初始化head和tail,初始化完成後,會繼續執行外面的死循環,進行compareAndSetTail將 //新節點設置到尾部,和上述執行流程同樣,這裏就不詳述了。 if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } } //再進行一次嘗試和進入堵塞 final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { //獲取當前node的前驅 final Node p = node.predecessor(); //若是前驅是head的話就再進行一次嘗試,這種設計會節約不少的資源。 //這裏嘗試成功後該線程就不會有後續的park和unpark之說了。 if (p == head && tryAcquire(arg)) { //若是獲取成功就將head設置成當前node,並將存儲的thread和prev都清空 setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } //來判斷進行嘗試獲取失敗後是否進行park private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { //node的waitStatus初始化都是0 int ws = pred.waitStatus; if (ws == Node.SIGNAL) //第一次進來確定不是-1狀態的,須要compareAndSetWaitStatus方法進行設置後纔會是-1 return true; if (ws > 0) { //這裏的做用是用來剔除被cancel後的節點,只要是cancel後的節點waitStatus 都會被標記成1。 //用該狀態來過濾掉這些節點。 //因爲節點的喚醒是由它的prev節點來進行喚醒的,咱們必需要保證它的prev是處於活着的狀態 //因此這裏一直遍歷往上找,總會找到一個正常的prev來幫助其unpark。 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { //設置prev爲-1狀態,(該狀態下可以喚醒它的下一個去幹活)。 //這裏結束後會跳到acquireQueued的死循環再次循環一次。 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } //要執行這個方法的前提是shouldParkAfterFailedAcquire這個方法必須返回true private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); } //阻塞線程的方法 public static void park(Object blocker) { Thread t = Thread.currentThread(); // 設置Blocker,設置爲當前lock。 setBlocker(t, blocker); // 等待獲取許可,這裏會進行堵塞,直到有人幫忙調用該線程的unpark方法纔會獲取到許可, //並繼續走下面的流程。 UNSAFE.park(false, 0L); // 設置Blocker,將該線程的parkBlocker字段設置爲null,這個是在線程被喚醒後執行的。 setBlocker(t, null); }
//調用該方法進行解鎖 public void unlock() { sync.release(1); } //改變state的值並喚醒隊列中的下一個線程來幹活 public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; //這裏會判斷頭部是否是null,並看其waitStatus 狀態是否有喚醒它的後繼節點的資格。 //這裏的頭部其實也就是當前線程所表明的節點。 if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } //嘗試着釋放鎖 protected final boolean tryRelease(int releases) { //將鎖標記state的值-1 int c = getState() - releases; //若是幹活的人和本身的身份不一致,則拋異常出去 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //這裏判斷狀態-1後是否是等於0。 //若是不是,則表明重入了不少次,鎖暫時不釋放。 //若是是,則將free置爲true,釋放鎖,將身份標記置爲null。 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } //去喚醒後繼節點中的thread來幹活 private void unparkSuccessor(Node node) { int ws = node.waitStatus; //若是head中的waitStatus<0,則置爲0 if (ws < 0) compareAndSetWaitStatus(node, ws, 0); //這裏會檢查head的下一個節點是否是null以及是不是cancel狀態 Node s = node.next; if (s == null || s.waitStatus > 0) { //若是next是cancel狀態,則將s置爲空,並重隊列尾部進行往前遍歷,直到找到最後 //一個waitStatus <=0的node來作爲next節點去喚醒 s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) //去喚醒s指向的next節點,調用這裏可讓UNSAFE.park(false, 0L);處的線程獲取到許可。 //到這裏解鎖的功能就執行完畢了~ LockSupport.unpark(s.thread); }
擴展個ReentrantReadWriteLock 讀鎖獲取鎖的流程圖
ui