<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ReentrantLock 中的 4 個坑!

          共 6801字,需瀏覽 14分鐘

           ·

          2021-08-17 01:35

          作者 | 王磊

          來源 | Java中文社群(ID:javacn666)

          轉(zhuǎn)載請聯(lián)系授權(quán)(微信ID:GG_Stone)

          JDK 1.5 之前 synchronized 的性能是比較低的,但在 JDK 1.5 中,官方推出一個重量級功能 Lock,一舉改變了 Java 中鎖的格局。JDK 1.5 之前當我們談到鎖時,只能使用內(nèi)置鎖 synchronized,但如今我們鎖的實現(xiàn)又多了一種顯式鎖 Lock。

          前面的文章我們已經(jīng)介紹了 synchronized,詳見以下列表:

          《synchronized 加鎖 this 和 class 的區(qū)別!》
          《synchronized 優(yōu)化手段之鎖膨脹機制!》
          《synchronized 中的 4 個優(yōu)化,你知道幾個?》

          所以本文咱們重點來看 Lock。

          Lock 簡介

          Lock 是一個頂級接口,它的所有方法如下圖所示:

          它的子類列表如下:

          我們通常會使用 ReentrantLock 來定義其實例,它們之間的關(guān)聯(lián)如下圖所示:

          PS:Sync 是同步鎖的意思,F(xiàn)airSync 是公平鎖,NonfairSync 是非公平鎖。

          ReentrantLock 使用

          學習任何一項技能都是先從使用開始的,所以我們也不例外,咱們先來看下 ReentrantLock 的基礎使用:

          public class LockExample {
          // 創(chuàng)建鎖對象
          private final ReentrantLock lock = new ReentrantLock();
          public void method() {
          // 加鎖操作
          lock.lock();
          try {
          // 業(yè)務代碼......
          } finally {
          // 釋放鎖
          lock.unlock();
          }
          }
          }

          ReentrantLock 在創(chuàng)建之后,有兩個關(guān)鍵性的操作:

          • 加鎖操作:lock()
          • 釋放鎖操作:unlock()

          ReentrantLock 中的坑

          1.ReentrantLock 默認為非公平鎖

          很多人會認為(尤其是新手朋友),ReentrantLock 默認的實現(xiàn)是公平鎖,其實并非如此,ReentrantLock 默認情況下為非公平鎖(這主要是出于性能方面的考慮),比如下面這段代碼:

          import java.util.concurrent.locks.ReentrantLock;

          public class LockExample {
          // 創(chuàng)建鎖對象
          private static final ReentrantLock lock = new ReentrantLock();

          public static void main(String[] args) {
          // 定義線程任務
          Runnable runnable = new Runnable() {
          @Override
          public void run() {
          // 加鎖
          lock.lock();
          try {
          // 打印執(zhí)行線程的名字
          System.out.println("線程:" + Thread.currentThread().getName());
          } finally {
          // 釋放鎖
          lock.unlock();
          }
          }
          };
          // 創(chuàng)建多個線程
          for (int i = 0; i < 10; i++) {
          new Thread(runnable).start();
          }
          }
          }

          以上程序的執(zhí)行結(jié)果如下:

          從上述執(zhí)行的結(jié)果可以看出,ReentrantLock 默認情況下為非公平鎖。因為線程的名稱是根據(jù)創(chuàng)建的先后順序遞增的,所以如果是公平鎖,那么線程的執(zhí)行應該是有序遞增的,但從上述的結(jié)果可以看出,線程的執(zhí)行和打印是無序的,這說明 ReentrantLock 默認情況下為非公平鎖。

          想要將 ReentrantLock 設置為公平鎖也很簡單,只需要在創(chuàng)建 ReentrantLock 時,設置一個 true 的構(gòu)造參數(shù)就可以了,如下代碼所示:

          import java.util.concurrent.locks.ReentrantLock;

          public class LockExample {
          // 創(chuàng)建鎖對象(公平鎖)
          private static final ReentrantLock lock = new ReentrantLock(true);

          public static void main(String[] args) {
          // 定義線程任務
          Runnable runnable = new Runnable() {
          @Override
          public void run() {
          // 加鎖
          lock.lock();
          try {
          // 打印執(zhí)行線程的名字
          System.out.println("線程:" + Thread.currentThread().getName());
          } finally {
          // 釋放鎖
          lock.unlock();
          }
          }
          };
          // 創(chuàng)建多個線程
          for (int i = 0; i < 10; i++) {
          new Thread(runnable).start();
          }
          }
          }

          以上程序的執(zhí)行結(jié)果如下:

          從上述結(jié)果可以看出,當我們顯式的給 ReentrantLock 設置了 true 的構(gòu)造參數(shù)之后,ReentrantLock 就變成了公平鎖,線程獲取鎖的順序也變成有序的了。

          其實從 ReentrantLock 的源碼我們也可以看出它究竟是公平鎖還是非公平鎖,ReentrantLock 部分源碼實現(xiàn)如下:

           public ReentrantLock() {
          sync = new NonfairSync();
          }
          public ReentrantLock(boolean fair) {
          sync = fair ? new FairSync() : new NonfairSync();
          }

          從上述源碼中可以看出,默認情況下 ReentrantLock 會創(chuàng)建一個非公平鎖,如果在創(chuàng)建時顯式的設置構(gòu)造參數(shù)的值為 true 時,它就會創(chuàng)建一個公平鎖。

          2.在 finally 中釋放鎖

          使用 ReentrantLock 時一定要記得釋放鎖,否則就會導致該鎖一直被占用,其他使用該鎖的線程則會永久的等待下去,所以我們在使用 ReentrantLock 時,一定要在 finally 中釋放鎖,這樣就可以保證鎖一定會被釋放。

          反例

          import java.util.concurrent.locks.ReentrantLock;

          public class LockExample {
          // 創(chuàng)建鎖對象
          private static final ReentrantLock lock = new ReentrantLock();
          public static void main(String[] args) {
          // 加鎖操作
          lock.lock();
          System.out.println("Hello,ReentrantLock.");
          // 此處會報異常,導致鎖不能正常釋放
          int number = 1 / 0;
          // 釋放鎖
          lock.unlock();
          System.out.println("鎖釋放成功!");
          }
          }

          以上程序的執(zhí)行結(jié)果如下:

          從上述結(jié)果可以看出,當出現(xiàn)異常時鎖未被正常釋放,這樣就會導致其他使用該鎖的線程永久的處于等待狀態(tài)。

          正例

          import java.util.concurrent.locks.ReentrantLock;

          public class LockExample {
          // 創(chuàng)建鎖對象
          private static final ReentrantLock lock = new ReentrantLock();
          public static void main(String[] args) {
          // 加鎖操作
          lock.lock();
          try {
          System.out.println("Hello,ReentrantLock.");
          // 此處會報異常
          int number = 1 / 0;
          } finally {
          // 釋放鎖
          lock.unlock();
          System.out.println("鎖釋放成功!");
          }
          }
          }

          以上程序的執(zhí)行結(jié)果如下:

          從上述結(jié)果可以看出,雖然方法中出現(xiàn)了異常情況,但并不影響 ReentrantLock 鎖的釋放操作,這樣其他使用此鎖的線程就可以正常獲取并運行了。

          3.鎖不能被釋放多次

          lock 操作的次數(shù)和 unlock 操作的次數(shù)必須一一對應,且不能出現(xiàn)一個鎖被釋放多次的情況,因為這樣就會導致程序報錯。

          反例

          一次 lock 對應了兩次 unlock 操作,導致程序報錯并終止執(zhí)行,示例代碼如下:

          import java.util.concurrent.locks.ReentrantLock;

          public class LockExample {
          // 創(chuàng)建鎖對象
          private static final ReentrantLock lock = new ReentrantLock();

          public static void main(String[] args) {
          // 加鎖操作
          lock.lock();

          // 第一次釋放鎖
          try {
          System.out.println("執(zhí)行業(yè)務 1~");
          // 業(yè)務代碼 1......
          } finally {
          // 釋放鎖
          lock.unlock();
          System.out.println("鎖釋鎖");
          }

          // 第二次釋放鎖
          try {
          System.out.println("執(zhí)行業(yè)務 2~");
          // 業(yè)務代碼 2......
          } finally {
          // 釋放鎖
          lock.unlock();
          System.out.println("鎖釋鎖");
          }
          // 最后的打印操作
          System.out.println("程序執(zhí)行完成.");
          }
          }

          以上程序的執(zhí)行結(jié)果如下:

          從上述結(jié)果可以看出,執(zhí)行第 2 個 unlock 時,程序報錯并終止執(zhí)行了,導致異常之后的代碼都未正常執(zhí)行。

          4.lock 不要放在 try 代碼內(nèi)

          在使用 ReentrantLock 時,需要注意不要將加鎖操作放在 try 代碼中,這樣會導致未加鎖成功就執(zhí)行了釋放鎖的操作,從而導致程序執(zhí)行異常。

          反例

          import java.util.concurrent.locks.ReentrantLock;

          public class LockExample {
          // 創(chuàng)建鎖對象
          private static final ReentrantLock lock = new ReentrantLock();

          public static void main(String[] args) {
          try {
          // 此處異常
          int num = 1 / 0;
          // 加鎖操作
          lock.lock();
          } finally {
          // 釋放鎖
          lock.unlock();
          System.out.println("鎖釋鎖");
          }
          System.out.println("程序執(zhí)行完成.");
          }
          }

          以上程序的執(zhí)行結(jié)果如下:

          從上述結(jié)果可以看出,如果將加鎖操作放在 try 代碼中,可能會導致兩個問題:

          1. 未加鎖成功就執(zhí)行了釋放鎖的操作,從而導致了新的異常;
          2. 釋放鎖的異常會覆蓋程序原有的異常,從而增加了排查問題的難度。

          總結(jié)

          本文介紹了 Java 中的顯式鎖 Lock 及其子類 ReentrantLock 的使用和注意事項,Lock 在 Java 中占據(jù)了鎖的半壁江山,但在使用時卻要注意 4 個問題:

          1. 默認情況下 ReentrantLock 為非公平鎖而非公平鎖;
          2. 加鎖次數(shù)和釋放鎖次數(shù)一定要保持一致,否則會導致線程阻塞或程序異常;
          3. 加鎖操作一定要放在 try 代碼之前,這樣可以避免未加鎖成功又釋放鎖的異常;
          4. 釋放鎖一定要放在 finally 中,否則會導致線程阻塞。

          文末福利

          今天恰好情人節(jié)磊哥聯(lián)合博文視點出版社,給大家送 3 本何海濤老師的經(jīng)典書籍《劍指Offer(專項突破版):數(shù)據(jù)結(jié)構(gòu)與算法名企面試題精講》,作者從微軟起步,面遍各國際大公司,且有近 20 年名企面試官經(jīng)歷,擔任面試官面試千余次,積累大量真實試題和現(xiàn)場經(jīng)驗。

          中獎規(guī)則:評論區(qū)留的第 6、16、26 位用戶送出此書,免費包郵到家,下周二開獎。

          當然,土豪朋友也可以通過下面連接直接購買。


          本系列原創(chuàng)文章推薦

          1.線程的故事:我的3位母親成就了優(yōu)秀的我!

          2.線程池的7種創(chuàng)建方式,強烈推薦你用它...

          3.輕量級鎖一定比重量級鎖快嗎?

          4.這樣終止線程,竟然會導致服務宕機?

          5.漫畫:如何證明sleep不釋放鎖,而wait釋放鎖?

          6.池化技術(shù)到達有多牛?看了這個對比嚇我一跳!

          7.求求你,別再用wait和notify了!

          8.Semaphore自白:限流器用我就對了!

          9.CountDownLatch:別浪,等人齊再團!

          10.CyclicBarrier:人齊了,老司機就發(fā)車了!

          11.Java中用戶線程和守護線程區(qū)別這么大?

          12.ThreadLocal不好用?那是你沒用對!

          13.ThreadLocal內(nèi)存溢出代碼演示和原因分析!

          14.SimpleDateFormat線程不安全的5種解決方案!

          15.synchronized 加鎖 this 和 class 的區(qū)別!

          16.synchronized 優(yōu)化手段之鎖膨脹機制!
          17.synchronized 中的 4 個優(yōu)化,你知道幾個?

          瀏覽 45
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黑人大屌啪啪 | 免费看天堂的逼 | 97视频网站 | 丁香五月婷婷色综合 | 黄色永久免费看 |