1. 程式人生 > >咀嚼Lock和Synchronized鎖

咀嚼Lock和Synchronized鎖

## 1.Synchronized鎖 底層是`monitor`監視器,每一個物件再建立的時候都會常見一個`monitor`監視器,在使用`synchronized`程式碼塊的時候,會在程式碼塊的前後產生一個`monitorEnter和monitorexit`指令,來標識這是一個同步程式碼塊。 #### 1.1 執行流程 執行緒遇到同步程式碼塊,給這個物件`monitor`物件加`1`,當執行緒退出當前程式碼塊以後,給這個物件的`monitor`物件減一,如果`monitor`指令的值為`0`則當前執行緒釋放鎖。 #### 1.2 反編譯原始碼 **同步程式碼塊反編譯** ```java public void test01(){ synchronized (this){ int num = 1 ; } } ``` ![](https://gitee.com/onlyzl/blogImage/raw/master/img/20200416174306.png) 兩次`monitorexit`的作用是避免同步程式碼塊無法跳出,因此存在兩種,**正常退出和異常退出** **同步方法反編譯** ```java public synchronized void test01(){ int num = 1 ; } ``` ![](https://gitee.com/onlyzl/blogImage/raw/master/img/20200416174422.png) 可以發現其沒有在同步方法前後新增`monitor`指令,但是在其底層實際上也是通過`monitor`指令實現的,只不過相較於同步程式碼塊來說,他是隱式的。 #### 1.3 鎖升級 在`JDK1.5`的時候對於`synchronzied`做了一系列優化操作,增加了諸如:偏向鎖,輕量級鎖,自旋鎖,鎖粗化,重量級鎖的概念。 ##### 1.3.1 偏向鎖 在一個執行緒在執行獲取鎖的時候,當前執行緒會在`monitor`物件中儲存指向該執行緒的ID。當執行緒再次進入的時候,不需要通過CAS的方法再來進行加鎖或者解鎖,而是檢測偏向鎖的ID是不是當前要進行的執行緒,如果是,直接進入。 偏向鎖,**適用於一個執行緒執行任務的情況** 在`JDK1.6`中,預設是開啟的。可以通過`-XX:-UseBiasedLocking=false`引數關閉偏向鎖 ##### 1.3.2 輕量級鎖 輕量級鎖是指鎖為偏向鎖的時候,該鎖被其他執行緒嘗試獲取,此時偏向鎖升級為輕量級鎖,其他執行緒會通過自旋的方式嘗試獲取鎖,執行緒不會阻塞,從而提供效能 升級為輕量級鎖的情況有兩種: - 關閉偏向鎖 - 有多個執行緒競爭偏向鎖的時候 **具體實現:** 執行緒進行程式碼塊以後,如果同步物件鎖狀態為無鎖的狀態,虛擬機器將首先在當前執行緒的棧幀中建立一個鎖記錄的空間。這個空間記憶體儲了當前獲取鎖的物件。 **使用情況:** 兩個執行緒的互相訪問 ##### 1.3.3 重量級鎖 在有超過2個執行緒訪問同一把鎖的時候,鎖自動升級為重量級鎖,也就是傳統的`synchronized`,此時其他未獲取鎖的執行緒會陷入等待狀態,不可被中斷。 由於依賴於`monitor`指令,所以其消耗系統資源比較大 **上面的三個階段就是鎖升級的過程** ##### 1.3.4 鎖粗化 當在一個迴圈中,我們多次使用對同一個程式碼進行加鎖,這個時候,JVM會自動實現鎖粗化,即在迴圈外進行新增同步程式碼塊。 **程式碼案例:** 鎖粗化之前: ```java for (int i = 0; i < 10; i++) { synchronized (LockBigDemo.class){ System.out.println(); } } ``` 鎖粗化之後: ```java synchronized (LockBigDemo.class){ for (int i = 0; i < 10; i++) { System.out.println(); } } ``` **本次關於`synchronized`的底層原理沒有以程式碼的方式展開,之後筆者會出一篇`synchronized`底層原理剖析的文章** ## 2. Lock鎖 一個類級別的鎖,需要手動釋放鎖。可以選擇性的選擇設定為公平鎖或者不公平鎖。等待執行緒可以被打斷。 底層是基於`AQS`+`AOS`。`AQS`類完成具體的加鎖邏輯,`AOS`儲存獲取鎖的執行緒資訊 #### 2.1 ReentrantLock 我們以`ReentrantLock`為例解析一下其加鎖的過程。 ##### 2.1.1 lock方法 首先通過`ReentrantLock`的構造方法的布林值判斷建立的鎖是公平鎖還是非公平鎖。 假設現在建立的是非公平鎖,他首先會判斷鎖有沒有被獲取,如果沒有被獲取,則直接獲取鎖; 如果鎖已經被獲取,執行一次自旋,嘗試獲取鎖。 如果鎖已經被獲取,則將當前執行緒封裝為`AQS`佇列的一個節點,然後判斷當前節點的前驅節點是不是`HEAD`節點,如果是,嘗試獲取鎖;如果不是。則尋找一個安全點(執行緒狀態位`SIGNAL=-1`的節點)。 開始不斷自旋。判斷前節點是不是`HEAD`節點,如果是獲取鎖,如果不是掛起。 ![](https://gitee.com/onlyzl/blogImage/raw/master/img/20200417215300.png) **原始碼解讀:** - 非公平鎖`lock` ```java final void lock() { //判斷是否存在鎖 if (compareAndSetState(0, 1)) //獲取鎖 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } ``` ```java public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } ``` ```java //非公平鎖的自旋邏輯 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); //獲取鎖狀態 int c = getState(); //如果鎖沒被獲取,獲取鎖 if (c == 0) { 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"); setState(nextc); return true; } return false; } ``` ```java //將執行緒封裝為一個執行緒節點,傳入鎖模式,排他或者共享 private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // 獲取尾節點 Node pred = tail; //如果尾節點不為Null,直接將這個執行緒節點新增到隊尾 if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //為空,自旋設定尾節點 enq(node); return node; } 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; } } } } ``` ```java //嘗試入隊 final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { //獲取節點的前驅節點,如果前驅節點為head節點,則嘗試獲取鎖 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); } } ``` ```java private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; //前驅節點已經安全 if (ws == Node.SIGNAL) return true; //前驅節點不安全,尋找一個執行緒狀態為`Signal`的節點作為前驅節點 if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { //否則直接設定這個前驅節點的執行緒等待狀態值 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } //中斷執行緒 private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); } ``` ![](https://gitee.com/onlyzl/blogImage/raw/master/img/20200417215559.png) ##### 2.1.2 unlock方法 **程式碼解讀:** ```java public void unlock() { sync.release(1); } ``` ```java public final boolean release(int arg) { //嘗試釋放鎖 if (tryRelease(arg)) { //獲取佇列頭元素,喚醒該執行緒節點,執行任務 Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } ``` ```java protected final boolean tryRelease(int releases) { int c = getState() - releases; //判斷是否為當前執行緒擁有鎖 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //釋放成功 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } ``` ```java private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } //喚醒下一個節點 if (s != null) LockSupport.unpark(s.thread); } ``` ##### 2.1.3 Node節點 ```java /** 共享鎖,讀鎖使用 */ static final Node SHARED = new Node(); /** 獨佔鎖*/ static final Node EXCLUSIVE = null; /** 不安全執行緒 */ static final int CANCELLED = 1; /** 需要進行執行緒喚醒的執行緒 */ static final int SIGNAL = -1; /**condition等待中 */ static final int CONDITION = -2; //執行緒等待狀態 volatile int waitStatus; volatile Node prev; volatile Node next; volatile Thread thread; Node nextWaiter; ``` ### 3. Lock鎖和Synchronized的區別 - `Lock`鎖是API層面,`synchronized`是`CPU`源語級別的 - `Lock`鎖等待執行緒可以被中斷,`synchronized`等待執行緒不可以被中斷 - `Lock`鎖可以指定公平鎖和非公平鎖,`synchronized`只能為非公平鎖 - `Lock`鎖需要主動釋放鎖,`synchronized`執行完程式碼塊以後自動釋放鎖 >
更多原創文章請關注筆者公眾號@Ma