<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                在 JDK 1.5 之前共享對象的協調機制只有 synchronized 和 volatile,在 JDK 1.5 中增加了新的機制 ReentrantLock,該機制的誕生并不是為了替代 synchronized,而是在 synchronized 不適用的情況下,提供一種可以選擇的高級功能。 我們本課時的面試題是,synchronized 和 ReentrantLock 是如何實現的?它們有什么區別? #### 典型回答 synchronized 屬于獨占式悲觀鎖,是通過 JVM 隱式實現的,synchronized 只允許同一時刻只有一個線程操作資源。 在 Java 中每個對象都隱式包含一個 monitor(監視器)對象,加鎖的過程其實就是競爭 monitor 的過程,當線程進入字節碼 monitorenter 指令之后,線程將持有 monitor 對象,執行 monitorexit 時釋放 monitor 對象,當其他線程沒有拿到 monitor 對象時,則需要阻塞等待獲取該對象。 ReentrantLock 是 Lock 的默認實現方式之一,它是基于 AQS(Abstract Queued Synchronizer,隊列同步器)實現的,它默認是通過非公平鎖實現的,在它的內部有一個 state 的狀態字段用于表示鎖是否被占用,如果是 0 則表示鎖未被占用,此時線程就可以把 state 改為 1,并成功獲得鎖,而其他未獲得鎖的線程只能去排隊等待獲取鎖資源。 synchronized 和 ReentrantLock 都提供了鎖的功能,具備互斥性和不可見性。在 JDK 1.5 中 synchronized 的性能遠遠低于 ReentrantLock,但在 JDK 1.6 之后 synchronized 的性能略低于 ReentrantLock,它的區別如下: * synchronized 是 JVM 隱式實現的,而 ReentrantLock 是 Java 語言提供的 API; * ReentrantLock 可設置為公平鎖,而 synchronized 卻不行; * ReentrantLock 只能修飾代碼塊,而 synchronized 可以用于修飾方法、修飾代碼塊等; * ReentrantLock 需要手動加鎖和釋放鎖,如果忘記釋放鎖,則會造成資源被永久占用,而 synchronized 無需手動釋放鎖; * ReentrantLock 可以知道是否成功獲得了鎖,而 synchronized 卻不行。 #### 考點分析 synchronized 和 ReentrantLock 是比線程池還要高頻的面試問題,因為它包含了更多的知識點,且涉及到的知識點更加深入,對面試者的要求也更高,前面我們簡要地介紹了 synchronized 和 ReentrantLock 的概念及執行原理,但很多大廠會更加深入的追問更多關于它們的實現細節,比如: * ReentrantLock 的具體實現細節是什么? * JDK 1.6 時鎖做了哪些優化? #### 知識擴展 * [ ] ReentrantLock 源碼分析 本課時從源碼出發來解密 ReentrantLock 的具體實現細節,首先來看 ReentrantLock 的兩個構造函數: ``` public ReentrantLock() { sync = new NonfairSync(); // 非公平鎖 } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } ``` 無參的構造函數創建了一個非公平鎖,用戶也可以根據第二個構造函數,設置一個 boolean 類型的值,來決定是否使用公平鎖來實現線程的調度。 * [ ] 公平鎖 VS 非公平鎖 公平鎖的含義是線程需要按照請求的順序來獲得鎖;而非公平鎖則允許“插隊”的情況存在,所謂的“插隊”指的是,線程在發送請求的同時該鎖的狀態恰好變成了可用,那么此線程就可以跳過隊列中所有排隊的線程直接擁有該鎖。 而公平鎖由于有掛起和恢復所以存在一定的開銷,因此性能不如非公平鎖,所以 ReentrantLock 和 synchronized 默認都是非公平鎖的實現方式。 ReentrantLock 是通過 lock() 來獲取鎖,并通過 unlock() 釋放鎖,使用代碼如下: ``` Lock lock = new ReentrantLock(); try { // 加鎖 lock.lock(); //......業務處理 } finally { // 釋放鎖 lock.unlock(); } ``` ReentrantLock 中的 lock() 是通過 sync.lock() 實現的,但 Sync 類中的 lock() 是一個抽象方法,需要子類 NonfairSync 或 FairSync 去實現,NonfairSync 中的 lock() 源碼如下: ``` final void lock() { if (compareAndSetState(0, 1)) // 將當前線程設置為此鎖的持有者 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } ``` FairSync 中的 lock() 源碼如下: ``` final void lock() { acquire(1); } ``` 可以看出非公平鎖比公平鎖只是多了一行 compareAndSetState 方法,該方法是嘗試將 state 值由 0 置換為 1,如果設置成功的話,則說明當前沒有其他線程持有該鎖,不用再去排隊了,可直接占用該鎖,否則,則需要通過 acquire 方法去排隊。 acquire 源碼如下: ``` public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } ``` tryAcquire 方法嘗試獲取鎖,如果獲取鎖失敗,則把它加入到阻塞隊列中,來看 tryAcquire 的源碼: ``` protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 公平鎖比非公平鎖多了一行代碼 !hasQueuedPredecessors() if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { //嘗試獲取鎖 setExclusiveOwnerThread(current); // 獲取成功,標記被搶占 return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); // set state=state+1 return true; } return false; } ``` 對于此方法來說,公平鎖比非公平鎖只多一行代碼 !hasQueuedPredecessors(),它用來查看隊列中是否有比它等待時間更久的線程,如果沒有,就嘗試一下是否能獲取到鎖,如果獲取成功,則標記為已經被占用。 如果獲取鎖失敗,則調用 addWaiter 方法把線程包裝成 Node 對象,同時放入到隊列中,但 addWaiter 方法并不會嘗試獲取鎖,acquireQueued 方法才會嘗試獲取鎖,如果獲取失敗,則此節點會被掛起,源碼如下: ``` /** * 隊列中的線程嘗試獲取鎖,失敗則會被掛起 */ 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); // 獲取成功,將當前節點設置為 head 節點 p.next = null; // 原 head 節點出隊,等待被 GC failed = false; // 獲取成功 return interrupted; } // 判斷獲取鎖失敗后是否可以掛起 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) // 線程若被中斷,返回 true interrupted = true; } } finally { if (failed) cancelAcquire(node); } } ``` 該方法會使用 for(;;) 無限循環的方式來嘗試獲取鎖,若獲取失敗,則調用 shouldParkAfterFailedAcquire 方法,嘗試掛起當前線程,源碼如下: ``` /** * 判斷線程是否可以被掛起 */ private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 獲得前驅節點的狀態 int ws = pred.waitStatus; // 前驅節點的狀態為 SIGNAL,當前線程可以被掛起(阻塞) if (ws == Node.SIGNAL) return true; if (ws > 0) { do { // 若前驅節點狀態為 CANCELLED,那就一直往前找,直到找到一個正常等待的狀態為止 node.prev = pred = pred.prev; } while (pred.waitStatus > 0); // 并將當前節點排在它后邊 pred.next = node; } else { // 把前驅節點的狀態修改為 SIGNAL compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } ``` 線程入列被掛起的前提條件是,前驅節點的狀態為 SIGNAL,SIGNAL 狀態的含義是后繼節點處于等待狀態,當前節點釋放鎖后將會喚醒后繼節點。所以在上面這段代碼中,會先判斷前驅節點的狀態,如果為 SIGNAL,則當前線程可以被掛起并返回 true;如果前驅節點的狀態 >0,則表示前驅節點取消了,這時候需要一直往前找,直到找到最近一個正常等待的前驅節點,然后把它作為自己的前驅節點;如果前驅節點正常(未取消),則修改前驅節點狀態為 SIGNAL。 到這里整個加鎖的流程就已經走完了,最后的情況是,沒有拿到鎖的線程會在隊列中被掛起,直到擁有鎖的線程釋放鎖之后,才會去喚醒其他的線程去獲取鎖資源,整個運行流程如下圖所示: ![](https://img.kancloud.cn/ca/01/ca013ad6861aff6a34c7c37187b81d7b_980x560.png) unlock 相比于 lock 來說就簡單很多了,源碼如下: ``` public void unlock() { sync.release(1); } public final boolean release(int arg) { // 嘗試釋放鎖 if (tryRelease(arg)) { // 釋放成功 Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } ``` 鎖的釋放流程為,先調用 tryRelease 方法嘗試釋放鎖,如果釋放成功,則查看頭結點的狀態是否為 SIGNAL,如果是,則喚醒頭結點的下個節點關聯的線程;如果釋放鎖失敗,則返回 false。 tryRelease 源碼如下: ``` /** * 嘗試釋放當前線程占有的鎖 */ protected final boolean tryRelease(int releases) { int c = getState() - releases; // 釋放鎖后的狀態,0 表示釋放鎖成功 // 如果擁有鎖的線程不是當前線程的話拋出異常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { // 鎖被成功釋放 free = true; setExclusiveOwnerThread(null); // 清空獨占線程 } setState(c); // 更新 state 值,0 表示為釋放鎖成功 return free; } ``` 在 tryRelease 方法中,會先判斷當前的線程是不是占用鎖的線程,如果不是的話,則會拋出異常;如果是的話,則先計算鎖的狀態值 getState() - releases 是否為 0,如果為 0,則表示可以正常的釋放鎖,然后清空獨占的線程,最后會更新鎖的狀態并返回執行結果。 #### JDK 1.6 鎖優化 * [ ] 自適應自旋鎖 JDK 1.5 在升級為 JDK 1.6 時,HotSpot 虛擬機團隊在鎖的優化上下了很大功夫,比如實現了自適應式自旋鎖、鎖升級等。 JDK 1.6 引入了自適應式自旋鎖意味著自旋的時間不再是固定的時間了,比如在同一個鎖對象上,如果通過自旋等待成功獲取了鎖,那么虛擬機就會認為,它下一次很有可能也會成功 (通過自旋獲取到鎖),因此允許自旋等待的時間會相對的比較長,而當某個鎖通過自旋很少成功獲得過鎖,那么以后在獲取該鎖時,可能會直接忽略掉自旋的過程,以避免浪費 CPU 的資源,這就是自適應自旋鎖的功能。 * [ ] 鎖升級 鎖升級其實就是從偏向鎖到輕量級鎖再到重量級鎖升級的過程,這是 JDK 1.6 提供的優化功能,也稱之為鎖膨脹。 偏向鎖是指在無競爭的情況下設置的一種鎖狀態。偏向鎖的意思是它會偏向于第一個獲取它的線程,當鎖對象第一次被獲取到之后,會在此對象頭中設置標示為“01”,表示偏向鎖的模式,并且在對象頭中記錄此線程的 ID,這種情況下,如果是持有偏向鎖的線程每次在進入的話,不再進行任何同步操作,如 Locking、Unlocking 等,直到另一個線程嘗試獲取此鎖的時候,偏向鎖模式才會結束,偏向鎖可以提高帶有同步但無競爭的程序性能。但如果在多數鎖總會被不同的線程訪問時,偏向鎖模式就比較多余了,此時可以通過 -XX:-UseBiasedLocking 來禁用偏向鎖以提高性能。 輕量鎖是相對于重量鎖而言的,在 JDK 1.6 之前,synchronized 是通過操作系統的互斥量(mutex lock)來實現的,這種實現方式需要在用戶態和核心態之間做轉換,有很大的性能消耗,這種傳統實現鎖的方式被稱之為重量鎖。 而輕量鎖是通過比較并交換(CAS,Compare and Swap)來實現的,它對比的是線程和對象的 Mark Word(對象頭中的一個區域),如果更新成功則表示當前線程成功擁有此鎖;如果失敗,虛擬機會先檢查對象的 Mark Word 是否指向當前線程的棧幀,如果是,則說明當前線程已經擁有此鎖,否則,則說明此鎖已經被其他線程占用了。當兩個以上的線程爭搶此鎖時,輕量級鎖就膨脹為重量級鎖,這就是鎖升級的過程,也是 JDK 1.6 鎖優化的內容。 #### 小結 本課時首先講了 synchronized 和 ReentrantLock 的實現過程,然后講了 synchronized 和 ReentrantLock 的區別,最后通過源碼的方式講了 ReentrantLock 加鎖和解鎖的執行流程。接著又講了 JDK 1.6 中的鎖優化,包括自適應式自旋鎖的實現過程,以及 synchronized 的三種鎖狀態和鎖升級的執行流程。 synchronized 剛開始為偏向鎖,隨著鎖競爭越來越激烈,會升級為輕量級鎖和重量級鎖。如果大多數鎖被不同的線程所爭搶就不建議使用偏向鎖了。 #### 課后問答 * 1、在自適應自旋鎖中,“而當某個鎖通過自旋很少成功獲得過鎖”有點理解不了。請問磊哥 鎖獲得鎖是什么情況? 講師回復: 可以理解為通過不停的循環獲得了(CPU的)執行權 * 2、而公平鎖由于有掛起和恢復所以存在一定的開銷,因此性能不如非公平鎖。非公平鎖也有掛起和恢復吧。 講師回復: 可以看下第六課時哈,有相應的答案。 * 3、老師,請問自旋鎖的自旋是什么意思?發現好多專有名詞 講師回復: 就是自己循環獲取鎖的意思 * 4、synchronized沒有結合markword分析加鎖的過程!如何進行鎖的升級的? 講師回復: 內容有限,源碼沒貼,但升級的過程有文字描述,可以自行對照源碼看一下。 * 5、意思是如果偏向鎖被禁用,那后邊的競爭機制是輕量級鎖嗎? 編輯回復: 對,偏向到輕量到重量這個過程 * 9、為什么兩個以上線程競爭鎖的時候會升級為重量級鎖?java中哪些是重量級鎖,我理解的是CAS是類似自旋的,多個就耗費CPU了,重量級會進休眠狀態? 講師回復: 文中有寫,JDK 1.6 以后引入自適應自旋了
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看