<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>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ## 1 更高級的鎖—深入解析Lock > 沒有智慧的頭腦,就象沒有臘燭的燈籠。 > ——列夫·托爾斯泰 前面的章節我們剛剛學習了 Java 的內置鎖,也就是 synchronized 關鍵字的使用。在 Java 5.0 之前只有 synchronized 和 volatile 可以用來進行同步。在 Java 5.0 之后,出現了新的同步機制,也就是使用 ReentrantLock 顯式的加鎖。而 ReentrantLock 的誕生并不是用來取代 synchroinized。而是應該在 synchroinized 無法滿足我們需求的時候才使用 ReentrantLock。 我們生活中也是一樣的,不要過分追求名牌、追求功能齊全。其實絕大多數情況下,我們選擇一般的產品已經足夠用了。一個產品 80% 的功能其實在你淘汰它之前都不會用到。當然,如果你確實有需求,那么還是應該選擇更為高級的產品。 ## 1、ReentrantLock 的使用 **簡單應用** ReentrantLock 的使用相比較 synchronized 會稍微繁瑣一點,所謂顯示鎖,也就是你在代碼中需要主動的去進行 lock 操作。一般來講我們可以按照下面的方式使用 ReentrantLock。 ~~~java Lock lock = new ReentrantLock(); lock.lock(); try { doSomething(); }finally { lock.unlock(); } ~~~ lock.lock () 就是在顯式的上鎖。上鎖后,下面的代碼塊一定要放到 try 中,并且要結合 finally 代碼塊調用 lock.unlock () 來釋放鎖,否則一定 doSomething 方法中出現任何異常,這個鎖將永遠不會被釋放掉。 **公平鎖和非公平鎖** synchronized 是非公平鎖,也就是說每當鎖匙放的時候,所有等待鎖的線程并不會按照排隊順去依次獲得鎖,而是會再次去爭搶鎖。ReentrantLock 相比較而言更為靈活,它能夠支持公平和非公平鎖兩種形式。只需要在聲明的時候傳入 true。 ~~~java Lock lock = new ReentrantLock(true); ~~~ 而默認的無參構造方法則會創建非公平鎖。 **tryLock** 前面我們通過 lock.lock (); 來完成加鎖,此時加鎖操作是阻塞的,直到獲取鎖才會繼續向下進行。ReentrantLock 其實還有更為靈活的枷鎖方式 tryLock。tryLock 方法有兩個重載,第一個是無參數的 tryLock 方法,被調用后,該方法會立即返回獲取鎖的情況。獲取為 true,未能獲取為 false。我們的代碼中可以通過返回的結果進行進一步的處理。第二個是有參數的 tryLock 方法,通過傳入時間和單位,來控制等待獲取鎖的時長。如果超過時間未能獲取鎖則放回 false,反之返回 true。使用方法如下: ~~~java if(lock.tryLock(2, TimeUnit.SECONDS)){ try { doSomething(); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } }else{ doSomethingElse(); } ~~~ 我們如果不希望無法獲取鎖時一直等待,而是希望能夠去做一些其它事情時,可以選擇此方式。 ## 2、lock 方法源碼分析 我們先從 lock 方法看起。lock 方法的代碼如下: ~~~java public void lock() { sync.lock(); } ~~~ 通過內置的 sync 對象加鎖,那么 sync 對象是什么呢?我們來看 ReentrantLock 的無參構造函數: ~~~java public ReentrantLock() { sync = new NonfairSync(); } ~~~ 有參的構造函數: ~~~java public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } ~~~ FairSync 和 NonFairSync 都繼承自 Sync。它們的繼承關系如下圖: ![圖片描述](https://img.mukewang.com/5dc37dfd00017b7806860361.jpg) 都是最終繼承自 AbstractQueuedSynchronizer。這就是 Java 中著名的 AQS。通過查看 AQS 的注釋我們了解到, AQS 依賴先進先出隊列實現了阻塞鎖和相關的同步器(信號量、事件等)。AQS 內部有一個 volatile 類型的 state 屬性,實際上多線程對鎖的競爭體現在對 state 值寫入的競爭。一旦 state 從 0 變為 1,代表有線程已經競爭到鎖,那么其它線程則進入等待隊列。等待隊列是一個鏈表結構的 FIFO 隊列,這能夠確保公平鎖的實現。同一線程多次獲取鎖時,如果之前該線程已經持有鎖,那么對 state 再次加 1。釋放鎖時,則會對 state-1。直到減為 0,才意味著此線程真正釋放了鎖。 ![圖片描述](https://img.mukewang.com/5dc37e130001f42308690399.jpg) 我們回過頭來,繼續跟進 sync.lock (); 的源代碼。我們對代碼的分析選擇公平鎖這條線。FairSync 實現的 lock 代碼很簡單: ~~~java final void lock() { acquire(1); } ~~~ 在 FairSync 并沒有重寫 acquire 方法代碼。調用的為 AbstractQueuedSynchronizer 的代碼,如下: ~~~java public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } ~~~ 首先調用一次 tryAcquire 方法。如果 tryAcquire 方法返回 true,那么 acquire 就會立即返回。但如果 tryAcquire 返回了 false,那么則會先調用 addWaiter,把當前線程包裝成一個等待的 node,加入到等待隊列。然后調用 acquireQueued 嘗試排隊獲取鎖,如果成功后發現自己被中斷過,那么返回 true,導致 selfInterrupt 被觸發,這個方里只是調用 Thread.currentThread ().interrupt (); 進行 interrupt。 acquireQueued 代碼如下: ~~~java 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); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } ~~~ 在此方法中進入自旋,不斷查看自己排隊的情況。如果輪到自己( header 是已經獲取鎖的線程,而 header 后面的線程是排隊到要去獲取鎖的線程),那么調用 tryAcquire 方法去獲取鎖,然后把自己設置為隊列的 header。在自旋中,如果沒有排隊到自己,還會檢查是否應該應該被中斷。 整個獲取鎖的過程我們可以總結下: 1. 直接通過 tryAcquire 嘗試獲取鎖,成功直接返回; 2. 如果沒能獲取成功,那么把自己加入等待隊列; 3. 自旋查看自己的排隊情況; 4. 如果排隊輪到自己,那么嘗試通過 tryAcquire 獲取鎖; 5. 如果沒輪到自己,那么回到第三步查看自己的排隊情況。 從以上過程我們可以看到鎖的獲取是通過 tryAcquire 方法。而這個方法在 FairSync 和 NonfairSync 有不同實現,我們來分析在 FairSync 中的實現。 ~~~java protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { 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); return true; } return false; } ~~~ 實際上它的實現和 NonfairSync 的實現,值是在 c==0 時,多了對 hasQueuedPredecessors 方法的調用。故名思義,這個方法做的事情就是判斷當前線程是否前面還有排隊的線程。當它前面沒有排隊線程,說明已經排隊到自己了,這是才會通過 CAS 的的方式去改變 state 值為 1,如果成功,那么說明當前線程獲取鎖成功。接下來就是調用 setExclusiveOwnerThread 把自己設置成為鎖的擁有者。else if 中邏輯則是在處理重入邏輯,如果當前線程就是鎖的擁有者,那么會把 state 加 1 更新回去。 通過以上分析,我們可以看出 AbstractQueuedSynchronizer 提供 acquire 方法的模板邏輯,但其中真正對鎖的獲取方法 tryAcquire,是在不同子類中實現的,這是很好的設計思想。 ## 3、unlock 方法源碼分析 下面我們來分析 unlock 的源碼: ~~~java public void unlock() { sync.release(1); } ~~~ 和 lock 很像,實際調用的是 sync 實現類的 release 方法。和 lock 方法一樣,這個 release 方法在 AbstractQueuedSynchronizer 中, ~~~java if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; ~~~ 這個方法中會先執行 tryRelease,它的實現也在 AbstractQueuedSynchronizer 的子類 Sync 中,如果釋放鎖成功,那么則會通過 unparkSuccessor 方法找到隊列中第一個 waitStatus<0 的線程進行喚醒。我們下面看一下 tryRelease 方法代碼: ~~~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; } ~~~ 還是比較簡單,釋放的時候會把 state 減 1,如果減到 0,那么說明沒有線程持有鎖,則會設置 free=true 并且清空鎖的持有者。如果 state 值還是大于 0,這說明可重入鎖還有其它線程持有,那么鎖并沒有被真正釋放,僅僅是減少了持有的數量,所以返回 false。 ## 總結 本節學習了 ReentrantLock 的使用及其核心源代碼,其實 Lock 相關的代碼還有很多。我們可以嘗試自己去閱讀。ReentrantLock 的設計思想是通過 FIFO 的隊列保存等待鎖的線程。通過 volatile 類型的 state 保存鎖的持有數量,從而實現了鎖的可重入性。而公平鎖則是通過判斷自己是否排隊成功,來決定是否去爭搶鎖。學習完本節相信你一定會有疑問,為什么在內置鎖之外又設計了 Lock 顯式鎖呢?下一節,我們將對這兩種鎖進行對比,看看各自適合的場景。
                  <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>

                              哎呀哎呀视频在线观看