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

          Java中的鎖詳解

          共 34302字,需瀏覽 69分鐘

           ·

          2021-04-17 19:13

          點擊上方藍色字體,選擇“標星公眾號”

          優(yōu)質(zhì)文章,第一時間送達

          Lock和synchronized

          • 鎖是一種工具,用于控制對共享資源的訪問

          • Lock和synchronized,這兩個是最創(chuàng)建的鎖,他們都可以達到線程安全的目的,但是使用和功能上有較大不同

          • Lock不是完全替代synchronized的,而是當(dāng)使用synchronized不合適或不足以滿足要求的時候,提供高級功能 

          • Lock 最常見的是ReentrantLock實現(xiàn)

          為啥需要Lock

          • syn效率低:鎖的釋放情況少,試圖獲得鎖時不能設(shè)定超時,不能中斷一個正在試圖獲得鎖的線程

          • 不夠靈活,加鎖和釋放的時機單一,每個鎖僅有一個單一的條件(某個對象),可能是不夠的

          • 無法知道是否成功獲取到鎖

          主要方法

          Lock();     

          最普通的獲取鎖,最佳實踐是finally中釋放鎖,保證發(fā)生異常的時候鎖一定被釋放

              /**
               * 描述:Lock不會像syn一樣,異常的時候自動釋放鎖
               *      所以最佳實踐是finally中釋放鎖,保證發(fā)生異常的時候鎖一定被釋放
               */
              private static Lock lock = new ReentrantLock();
           
              public static void main(String[] args) {
                  lock.lock();
                  try {
                      //獲取本鎖保護的資源
                      System.out.println(Thread.currentThread().getName() + "開始執(zhí)行任務(wù)");
                  } finally {
                      lock.unlock();
                  }
              }

          tryLock(long time,TimeUnit unit);超時就放棄

          用來獲取鎖,如果當(dāng)前鎖沒有被其它線程占用,則獲取成功,則返回true,否則返回false,代表獲取鎖失敗

          /**
               * 描述:用TryLock避免死鎖
               */
              static class TryLockDeadlock implements Runnable {
           
                  int flag = 1;
           
                  static Lock lock1 = new ReentrantLock();
                  static Lock lock2 = new ReentrantLock();
           
                  @Override
                  public void run() {
                      for (int i = 0; i < 100; i++) {
                          if (flag == 1) {
                              try {
                                  if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
                                      try {
                                          System.out.println("線程1獲取到了鎖1");
                                          Thread.sleep(new Random().nextInt(1000));
                                          if (lock2.tryLock(800,TimeUnit.MILLISECONDS)){
                                              try {
                                                  System.out.println("線程1獲取到了鎖2");
                                                  System.out.println("線程1成功獲取到了2把鎖");
                                                  break;
                                              }finally {
                                                  lock2.unlock();
                                              }
                                          }else{
                                              System.out.println("線程1獲取鎖2失敗,已重試");
                                          }
                                      } finally {
                                          lock1.unlock();
                                          Thread.sleep(new Random().nextInt(1000));
                                      }
                                  } else {
                                      System.out.println("線程1獲取鎖1失敗,已重試");
                                  }
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                          }
           
                          if (flag == 0) {
                              try {
                                  if (lock2.tryLock(3000, TimeUnit.MILLISECONDS)) {
                                      try {
                                          System.out.println("線程2獲取到了鎖2");
                                          Thread.sleep(new Random().nextInt(1000));
                                          if (lock1.tryLock(800,TimeUnit.MILLISECONDS)){
                                              try {
                                                  System.out.println("線程2獲取到了鎖1");
                                                  System.out.println("線程2成功獲取到了2把鎖");
                                                  break;
                                              }finally {
                                                  lock1.unlock();
                                              }
                                          }else{
                                              System.out.println("線程2獲取鎖1失敗,已重試");
                                          }
                                      } finally {
                                          lock2.unlock();
                                          Thread.sleep(new Random().nextInt(1000));
                                      }
                                  } else {
                                      System.out.println("線程2獲取鎖2失敗,已經(jīng)重試");
                                  }
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                          }
                      }
                  }
           
                  public static void main(String[] args) {
                      TryLockDeadlock r1 = new TryLockDeadlock();
                      TryLockDeadlock r2 = new TryLockDeadlock();
                      r1.flag = 1;
                      r2.flag = 0;
                      new Thread(r1).start();
                      new Thread(r2).start();
                  }
              }
           
          執(zhí)行結(jié)果:
          線程1獲取到了鎖1
          線程2獲取到了鎖2
          線程1獲取鎖2失敗,已重試
          線程2獲取到了鎖1
          線程2成功獲取到了2把鎖
          線程1獲取到了鎖1
          線程1獲取到了鎖2
          線程1成功獲取到了2把鎖

          lockInterruptibly(); 中斷

          相當(dāng)于tryLock(long time,TimeUnit unit) 把超時時間設(shè)置為無限,在等待鎖的過程中,線程可以被中斷

          /**
               * 描述:獲取鎖的過程中,中斷了
               */
              static class LockInterruptibly implements Runnable {
           
                  private Lock lock = new ReentrantLock();
           
                  @Override
                  public void run() {
                      System.out.println(Thread.currentThread().getName() + "嘗試獲取鎖");
                      try {
                          lock.lockInterruptibly();
                          try {
                              System.out.println(Thread.currentThread().getName() + "獲取到了鎖");
                              Thread.sleep(5000);
                          } catch (InterruptedException e) {
                              System.out.println(Thread.currentThread().getName() + "睡眠中被中斷了");
                          } finally {
                              lock.unlock();
                              System.out.println(Thread.currentThread().getName() + "釋放了鎖");
                          }
                      } catch (InterruptedException e) {
                          System.out.println(Thread.currentThread().getName() + "等鎖期間被中斷了");
                      }
                  }
           
                  public static void main(String[] args) {
                      LockInterruptibly lockInterruptibly = new LockInterruptibly();
                      Thread thread0 = new Thread(lockInterruptibly);
                      Thread thread1 = new Thread(lockInterruptibly);
                      thread0.start();
                      thread1.start();
                      try {
                          Thread.sleep(2000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      thread0.interrupt();
                  }
              }
           
          執(zhí)行結(jié)果:
          Thread-0嘗試獲取鎖
          Thread-1嘗試獲取鎖
          Thread-0獲取到了鎖
          Thread-0睡眠中被中斷了
          Thread-0釋放了鎖
          Thread-1獲取到了鎖
          Thread-1釋放了鎖

          Java鎖分類:


          樂觀鎖和悲觀鎖:

          樂觀鎖:

          • 比較樂觀,認為自己在處理操作的時候,不會有其它線程來干擾,所以并不會鎖住操作對象

          • 在更新的時候,去對比我修改期間的數(shù)據(jù)有沒有被改變過,如沒有,就正常的修改數(shù)據(jù)

          • 如果數(shù)據(jù)和我一開始拿到的不一樣了,說明其他人在這段時間內(nèi)改過,會選擇放棄,報錯,重試等策略

          • 樂觀鎖的實現(xiàn)一般都是利用CAS算法來實現(xiàn)的

          劣勢:

          可能造成ABA問題,就是不知道是不是修改過

          使用場景:

          適合并發(fā)寫入少的情況,大部分是讀取的場景,不加鎖的能讓讀取的性能大幅提高

          悲觀鎖:

          比較悲觀,認為如果我不鎖住這個資源,別人就會來爭搶,就會造成數(shù)據(jù)結(jié)果錯誤,所以它會鎖住操作對象,Java中悲觀鎖的實現(xiàn)就是syn和Lock相關(guān)類

          劣勢:

          • 阻塞和喚醒帶來的性能劣勢

          • 如果持有鎖的線程被永久阻塞,比如遇到了無限循環(huán),死鎖等活躍性問題,那么等待該線程釋放鎖的那幾個線程,永遠也得不到執(zhí)行

          • 優(yōu)先級反轉(zhuǎn),優(yōu)先級低的線程拿到鎖不釋放或釋放的比較慢,就會造成這個問題

          使用場景:

          適合并發(fā)寫入多的情況,適用于臨界區(qū)持鎖時間比較長的情況:

          • 臨界區(qū)有IO操作

          • 臨界區(qū)代碼復(fù)雜或者循環(huán)量大

          • 臨界區(qū)競爭非常激烈

          可重入鎖:

          • 可重入就是說某個線程已經(jīng)獲得某個鎖,可以再次獲取鎖而不會出現(xiàn)死鎖

          • ReentrantLock 和 synchronized 都是可重入鎖

          // 遞歸調(diào)用演示可重入鎖
              static class RecursionDemo{
           
                  public static ReentrantLock lock = new ReentrantLock();
           
                  private static void accessResource(){
                      lock.lock();
                      try {
                          System.out.println("已經(jīng)對資源處理了");
                          if (lock.getHoldCount() < 5){
                              System.out.println("已經(jīng)處理了"+lock.getHoldCount()+"次");
                              accessResource();
                          }
                      }finally {
                          lock.unlock();
                      }
                  }
           
                  public static void main(String[] args) {
                      new RecursionDemo().accessResource();
                  }
              }
           
           
          執(zhí)行結(jié)果:
          已經(jīng)對資源處理了
          已經(jīng)處理了1次
          已經(jīng)對資源處理了
          已經(jīng)處理了2次
          已經(jīng)對資源處理了
          已經(jīng)處理了3次
          已經(jīng)對資源處理了
          已經(jīng)處理了4次
          已經(jīng)對資源處理了

          ReentrantLock的其它方法

          • isHeldByCurrentThread 可以看出鎖是否被當(dāng)前線程持有

          • getQueueLength()可以返回當(dāng)前正在等待這把鎖的隊列有多長,一般這兩個方法是開發(fā)和調(diào)試時候使用,上線后用到的不多

          公平鎖和非公平鎖

          • 公平指的是按照線程請求的順序,來分配鎖;

          • 非公平指的是,不完全按照請求的順序,在一定情況下,可以插隊

          • 非公平鎖可以避免喚醒帶來的空檔期

          /**
           * 描述:演示公平鎖和非公平鎖
           */
          class FairLock{
           
              public static void main(String[] args) {
                  PrintQueue printQueue = new PrintQueue();
                  Thread[] thread = new Thread[10];
                  for (int i = 0; i < 10; i++) {
                      thread[i] = new Thread(new Job(printQueue));
                  }
           
                  for (int i = 0; i < 5; i++) {
                      thread[i].start();
                      try {
                          Thread.sleep(100);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              }
           
          }
           
          class Job implements Runnable{
           
              PrintQueue printQueue;
           
              public Job(PrintQueue printQueue) {
                  this.printQueue = printQueue;
              }
           
              @Override
              public void run() {
                  System.out.println(Thread.currentThread().getName()+"開始打印");
                  printQueue.printJob(new Object());
                  System.out.println(Thread.currentThread().getName()+"打印完成");
              }
          }
           
          class PrintQueue{    
              // true 公平,false是非公平
              private  Lock queueLock = new ReentrantLock(true);
              public void printJob(Object document){
                  queueLock.lock();
                  try {
                      int duration = new Random().nextInt(10)+1;
                      System.out.println(Thread.currentThread().getName()+"正在打印,需要"+duration+"秒");
                      Thread.sleep(duration * 1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  } finally {
                      queueLock.unlock();
                  }
           
                  queueLock.lock();
                  try {
                      int duration = new Random().nextInt(10)+1;
                      System.out.println(Thread.currentThread().getName()+"正在打印,需要"+duration+"秒");
                      Thread.sleep(duration * 1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  } finally {
                      queueLock.unlock();
                  }
           
              }
          }
           
          執(zhí)行結(jié)果:
          Thread-0開始打印
          Thread-0正在打印,需要10秒
          Thread-1開始打印
          Thread-2開始打印
          Thread-3開始打印
          Thread-4開始打印
          Thread-1正在打印,需要2秒
          Thread-2正在打印,需要2秒
          Thread-3正在打印,需要2秒
          Thread-4正在打印,需要4秒
          Thread-0正在打印,需要2秒
          Thread-0打印完成
          Thread-1正在打印,需要7秒
          Thread-1打印完成
          Thread-2正在打印,需要8秒
          Thread-2打印完成
          Thread-3正在打印,需要3秒
          Thread-3打印完成
          Thread-4正在打印,需要8秒
          Thread-4打印完成
           
          true改為false演示非公平鎖:
          Lock queueLock = new ReentrantLock(false);
          執(zhí)行結(jié)果:
          Thread-0正在打印,需要7秒
          Thread-1開始打印
          Thread-2開始打印
          Thread-3開始打印
          Thread-4開始打印
          Thread-0正在打印,需要9秒
          Thread-0打印完成
          Thread-1正在打印,需要3秒
          Thread-1正在打印,需要2秒
          Thread-1打印完成
          Thread-2正在打印,需要4秒
          Thread-2正在打印,需要7秒
          Thread-2打印完成
          Thread-3正在打印,需要10秒
          Thread-3正在打印,需要2秒
          Thread-3打印完成
          Thread-4正在打印,需要7秒
          Thread-4正在打印,需要8秒
          Thread-4打印完成

          共享鎖和排它鎖:

          • 排它鎖,又稱為獨占鎖,獨享鎖

          • 共享鎖,又稱為讀鎖,獲得共享鎖之后,可以查看但無法修改和刪除數(shù)據(jù),其他線程此時也可以獲取到共享鎖,也可以查看但無法修改和刪除數(shù)據(jù)

          • 共享鎖和排它鎖的典型是讀寫鎖 ReentrantReadWriteLock,其中讀鎖是共享鎖,寫鎖是獨享鎖

          讀寫鎖的作用:

          • 在沒有讀寫鎖之前,我們假設(shè)使用ReentrantLock,那么雖然我們保證了線程安全,但是也浪費了一定的資源:多個讀操作同時進行,并沒有線程安全問題

          • 在讀的地方使用讀鎖,在寫的地方使用寫鎖,靈活控制,如果沒有寫鎖的情況下,讀是無阻塞的,提高了程序的執(zhí)行效率

          讀寫鎖的規(guī)則:

          • 多個線程值申請讀鎖,都可以申請到

          • 要么一個或多個一起讀,要么一個寫,兩者不會同時申請到,只能存在一個寫鎖

          /**
           * 描述:演示可以多個一起讀,只能一個寫
           */
          class CinemaReadWrite{
              private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
              private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
              private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
           
              private static void read(){
                  readLock.lock();
                  try {
                      System.out.println(Thread.currentThread().getName() + "得到了讀鎖,正在讀取");
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  } finally {
                      System.out.println(Thread.currentThread().getName() + "釋放了讀鎖");
                      readLock.unlock();
                  }
              }
           
              private static void write(){
                  writeLock.lock();
                  try {
                      System.out.println(Thread.currentThread().getName() + "得到了寫鎖,正在寫入");
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  } finally {
                      System.out.println(Thread.currentThread().getName() + "釋放了寫鎖");
                      writeLock.unlock();
                  }
              }
           
              public static void main(String[] args) {
                  new Thread(()-> read(),"Thrad1").start();
                  new Thread(()-> read(),"Thrad2").start();
                  new Thread(()-> write(),"Thrad3").start();
                  new Thread(()-> write(),"Thrad4").start();
              }
          }
           
          執(zhí)行結(jié)果:
          Thrad1得到了讀鎖,正在讀取
          Thrad2得到了讀鎖,正在讀取
          Thrad2釋放了讀鎖
          Thrad1釋放了讀鎖
          Thrad3得到了寫鎖,正在寫入
          Thrad3釋放了寫鎖
          Thrad4得到了寫鎖,正在寫入
          Thrad4釋放了寫鎖

          讀鎖和寫鎖的交互方式:

          讀鎖插隊策略:

          • 公平鎖:不允許插隊

          • 非公平鎖:寫鎖可以隨時插隊,讀鎖僅在等待隊列頭節(jié)點不是想獲取寫鎖線程的時候可以插隊

          自旋鎖和阻塞鎖

          • 讓當(dāng)前線程進行自旋,如果自旋完成后前面鎖定同步資源的線程已經(jīng)釋放了鎖,那么當(dāng)前線程就可以不必阻塞而是直接獲取同步資源,從而避免切換線程的開銷。這就是自旋鎖。

          • 阻塞鎖和自旋鎖相反,阻塞鎖如果遇到?jīng)]拿到鎖的情況,會直接把線程阻塞,知道被喚醒

          自旋缺點:

          • 如果鎖被占用的時間很長,那么自旋的線程只會白浪費處理器資源

          • 在自旋的過程中,一直消耗cpu,所以雖然自旋鎖的起始開銷低于悲觀鎖,但是隨著自旋的時間增長,開銷也是線性增長的

          原理:

          • 在Java1.5版本及以上的并發(fā)框架java.util.concurrent 的atmoic包下的類基本都是自旋鎖的實現(xiàn)

          • AtomicInteger的實現(xiàn):自旋鎖的實現(xiàn)原理是CAS,AtomicInteger中調(diào)用unsafe 進行自增操作的源碼中的do-while循環(huán)就是一個自旋操作,如果修改過程中遇到其他線程競爭導(dǎo)致沒修改成功,就在while里死循環(huán)直至修改成功

          /**
           * 描述:自旋鎖演示
           */
          class SpinLock{
              private AtomicReference<Thread> sign = new AtomicReference<>();
           
              public void lock(){
                  Thread currentThread = Thread.currentThread();
                  while (!sign.compareAndSet(null,currentThread)){
                      System.out.println("自旋獲取失敗,再次嘗試");
                  }
              }
           
              public void unLock(){
                  Thread currentThread = Thread.currentThread();
                  sign.compareAndSet(currentThread,null);
              }
           
              public static void main(String[] args) {
                  SpinLock spinLock = new SpinLock();
                  Runnable runnable = new Runnable(){
                      @Override
                      public void run(){
                          System.out.println(Thread.currentThread().getName()+"開始嘗試自旋鎖");
                          spinLock.lock();
                          System.out.println(Thread.currentThread().getName()+"獲取到了自旋鎖");
                          try {
                              Thread.sleep(1);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }finally {
                              spinLock.unLock();
                              System.out.println(Thread.currentThread().getName()+"釋放了自旋鎖");
                          }
                      }
                  };
           
                  Thread thread1 = new Thread(runnable);
                  Thread thread2 = new Thread(runnable);
                  thread1.start();
                  thread2.start();
              }
          }
           
           
          執(zhí)行結(jié)果:
          Thread-0開始嘗試自旋鎖
          Thread-0獲取到了自旋鎖
          Thread-1開始嘗試自旋鎖
          自旋獲取失敗,再次嘗試
          自旋獲取失敗,再次嘗試
          自旋獲取失敗,再次嘗試
          自旋獲取失敗,再次嘗試
          自旋獲取失敗,再次嘗試
          自旋獲取失敗,再次嘗試
          自旋獲取失敗,再次嘗試
          自旋獲取失敗,再次嘗試
          自旋獲取失敗,再次嘗試
          自旋獲取失敗,再次嘗試
          自旋獲取失敗,再次嘗試
          Thread-0釋放了自旋鎖
          Thread-1獲取到了自旋鎖
          Thread-1釋放了自旋鎖

          使用場景:

          • 自旋鎖一般用于多核服務(wù)器,在并發(fā)度不是特別高的情況下,比阻塞鎖的效率要高

          • 另外,自旋鎖適用于臨界區(qū)比較短小的情況,否則如果臨界區(qū)很大(線程一旦拿到鎖,很久之后才會釋放),那也是不合適的

          ————————————————

          版權(quán)聲明:本文為CSDN博主「法毅的博客」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。

          原文鏈接:

          https://blog.csdn.net/sinat_35395498/article/details/115571519






          粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

          ??????

          ??長按上方微信二維碼 2 秒


          感謝點贊支持下哈 

          瀏覽 20
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产精品成人99一区无码 | 波多野结衣高清无码视频 | 淫色淫色网站 | 激情1234 | 日韩精品视频一区二区三区 |