深入理解Java中的鎖(二)

来源:https://www.cnblogs.com/coding-diary/archive/2019/07/25/11247039.html
-Advertisement-
Play Games

locks包結構層次 Lock 介面 方法簽名描述 void lock(); 獲取鎖(不死不休) boolean tryLock(); 獲取鎖(淺嘗輒止) boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 獲 ...


locks包結構層次

img

Lock 介面

方法簽名描述
void lock(); 獲取鎖(不死不休)
boolean tryLock(); 獲取鎖(淺嘗輒止)
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 獲取鎖(過時不候)
void lockInterruptibly() throws InterruptedException; 獲取鎖(任人擺佈)
void unlock(); 釋放鎖
Condition newCondition();  

代碼示例:

public class GetLockDemo {

  // 公平鎖
  // static Lock lock =new ReentrantLock(true);

  // 非公平鎖
  static Lock lock = new ReentrantLock();

  public static void main(String[] args) throws InterruptedException {
    // 主線程 拿到鎖
    lock.lock();

    Thread thread =
        new Thread(
            () -> {
              // 子線程 獲取鎖(不死不休)
              System.out.println("begain to get lock...");
              lock.lock();
              System.out.println("succeed to get lock...");

              //              // 子線程 獲取鎖(淺嘗輒止)
              //              boolean result = lock.tryLock();
              //              System.out.println("是否獲得到鎖:" + result);
              //
              //              // 子線程 獲取鎖(過時不候)
              //              try {
              //                boolean result1 = lock.tryLock(5, TimeUnit.SECONDS);
              //                System.out.println("是否獲得到鎖:" + result1);
              //              } catch (InterruptedException e) {
              //                e.printStackTrace();
              //              }
              //
              //              // 子線程 獲取鎖(任人擺佈)
              //              try {
              //                System.out.println("start to get lock Interruptibly");
              //                lock.lockInterruptibly();
              //              } catch (InterruptedException e) {
              //                e.printStackTrace();
              //                System.out.println("dad asked me to stop...");
              //              }

            });

    thread.start();
    Thread.sleep(10000L);
    lock.unlock();
  }
}

結論:

  • lock() 最常用
  • lockInterruptibly() 方法一般更昂貴,有的實現類可能沒有實現 lockInterruptible() 方法。只有真的需要用中斷時,才使用,使用前應看清實現類對該方法的描述。

Condition

Object中的wait(), notify(), notifyAll()方法是和synchronized配合使用的可以喚醒一個或者多個線程。Condition是需要與Lock配合使用的,提供多個等待集合和更精確的控制(底層是park/unpark機制);

協作方式死鎖方式1 (鎖)死鎖方式2(先喚醒,再掛起)備註
suspend/resume 死鎖 死鎖 棄用
wait/notify 不死鎖 死鎖 只用於synchronized關鍵字
park/unpark 死鎖 不死鎖  
condition 不死鎖 死鎖  

condition代碼示例:

public class ConditionDemo {

  static Lock lock = new ReentrantLock();

  static Condition condition = lock.newCondition();

  public static void main(String[] args) throws InterruptedException {
    Thread thread =
        new Thread(
            () -> {
              lock.lock();
              System.out.println("condition.await()");
              try {
                condition.await();
                System.out.println("here i am...");
              } catch (InterruptedException e) {
                e.printStackTrace();
              } finally {
                lock.unlock();
              }
            });
    thread.start();

    Thread.sleep(2000L);
    lock.lock();

    condition.signalAll();

    lock.unlock();
  }
}

ReetrantLock

ReentrantLock是可重入鎖,同一線程可以多次獲取到鎖

img

ReentrantLock實現原理分析

  1. ReentrantLock需要一個owner用來標記那個線程獲取到了鎖,一個count用來記錄加鎖的次數和一個waiters等待隊列用來存放沒有搶到鎖的線程列表
  2. 當有線程進來時,會先判斷count的值,如果count為0說明鎖沒有被占用
  3. 然後通過CAS操作進行搶鎖
  4. 如果搶到鎖則count的值會加1,同時將owner設置為當前線程的引用
  5. 如果count不為0同時owner指向當前線程的引用,則將count的值加1
  6. 如果count不為0同時owner指向的不是當前線程的引用,則將線程放入等待隊列waiters中
  7. 如果CAS搶鎖失敗,則將線程放入等待隊列waiters中
  8. 當線程使用完鎖後,會釋放其持有的鎖,釋放鎖時會將count的值減1,如果count值為0則將owner設為null
  9. 如果count值不為0則會喚醒等待隊列頭部的線程進行搶鎖

手動實現ReentrantLock代碼示例:

public class MyReentrantLock implements Lock {

  // 標記重入次數的count值
  private AtomicInteger count = new AtomicInteger(0);

  // 鎖的擁有者
  private AtomicReference<Thread> owner = new AtomicReference<>();

  // 等待隊列
  private LinkedBlockingDeque<Thread> waiters = new LinkedBlockingDeque<>();

  @Override
  public boolean tryLock() {
    // 判斷count是否為0,若count!=0,說明鎖被占用
    int ct = count.get();
    if (ct != 0) {
      // 判斷鎖是否被當前線程占用,若被當前線程占用,做重入操作,count+=1
      if (owner.get() == Thread.currentThread()) {
        count.set(ct + 1);
        return true;
      } else {
        // 若不是當前線程占用,互斥,搶鎖失敗,return false
        return false;
      }
    } else {
      // 若count=0, 說明鎖未被占用,通過CAS(0,1) 來搶鎖
      if (count.compareAndSet(ct, ct + 1)) {
        // 若搶鎖成功,設置owner為當前線程的引用
        owner.set(Thread.currentThread());
        return true;
      } else {
        return false;
      }
    }
  }

  @Override
  public void lock() {
    // 嘗試搶鎖
    if (!tryLock()) {
      // 如果失敗,進入等待隊列
      waiters.offer(Thread.currentThread());

      // 自旋
      for (; ; ) {
        // 判斷是否是隊列頭部,如果是
        Thread head = waiters.peek();
        if (head == Thread.currentThread()) {
          // 再次嘗試搶鎖
          if (!tryLock()) {
            // 若搶鎖失敗,掛起線程,繼續等待
            LockSupport.park();
          } else {
            // 若成功,就出隊列
            waiters.poll();
            return;
          }
        } else {
          // 如果不是隊列頭部,就掛起線程
          LockSupport.park();
        }
      }
    }
  }

  public boolean tryUnlock() {
    // 判斷,是否是當前線程占有鎖,若不是,拋異常
    if (owner.get() != Thread.currentThread()) {
      throw new IllegalMonitorStateException();
    } else {
      // 如果是,就將count-1  若count變為0 ,則解鎖成功
      int ct = count.get();
      int nextc = ct - 1;
      count.set(nextc);
      // 判斷count值是否為0
      if (nextc == 0) {
        owner.compareAndSet(Thread.currentThread(), null);
        return true;
      } else {
        return false;
      }
    }
  }

  @Override
  public void unlock() {
    // 嘗試釋放鎖
    if (tryUnlock()) {
      // 獲取隊列頭部, 如果不為null則將其喚醒
      Thread thread = waiters.peek();
      if (thread != null) {
        LockSupport.unpark(thread);
      }
    }
  }

  @Override
  public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
    return false;
  }

  @Override
  public void lockInterruptibly() throws InterruptedException {}

  @Override
  public Condition newCondition() {
    return null;
  }
}

synchronized VS Lock

synchronized

優點:

  • 使用簡單,語義清晰,哪裡需要點哪裡
  • 由JVM提供,提供了多種優化方案(鎖粗化,鎖消除,偏向鎖,輕量級鎖)
  • 鎖的釋放由虛擬機完成,不用人工干預,降低了死鎖的可能性

缺點:悲觀的排他鎖,無法實現鎖的高級功能如公平鎖,讀寫鎖等

Lock

優點:可以實現synchronized無法實現的鎖的高級功能如公平鎖,讀寫鎖等,同時還可以實現更多的功能 

缺點:需手動釋放鎖unlock,使用不當容易造成死鎖

 

結論: 兩者都是可重入鎖,synchronized可以類比為傻瓜相機,提供了固定的功能,而Lock可以類比為單方,可以根據需要調節所需的功能

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Python提供了切片(Slice)操作符:可以一次取出多個列表元素 L[0:3]表示,從索引0開始取,直到索引3為止,但不包括索引3。0可以省略:L[:3] L[:]:就是整個列表 補充: 前10個數,每兩個取一個: >>> L[:10:2] [0, 2, 4, 6, 8] s[:2:-1]表示從 ...
  • DDL、DML、DCL、DQL的簡單操作,從溫習中學到之前沒有體會到的一些知識,在忙碌中偷得一日閑。 ...
  • ​目錄內容 DOS命令 電腦配置 Java語言的特性 Java兩種核心機制 Java語言環境搭建 第一個Java程式 註釋 Java語句說明 編程風格 作業 DOS命令 電腦配置 Java語言的特性 Java兩種核心機制 Java語言環境搭建 第一個Java程式 註釋 Java語句說明 編程風格 作 ...
  • program ex implicit none character(len=40) A(3000),B(3000),C(3000) !A異常、B已開挖、C需標記 integer i,j,N1,N2,count !N1是10號文件行數,N2是11號文件行數,count是計數器 open(unit=1... ...
  • 9.11 進程池與線程池 池子使用來限制併發的任務數目,限制我們的電腦在一個自己可承受的範圍內去併發地執行任務 池子內什麼時候裝進程:併發的任務屬於計算密集型 池子內什麼時候裝線程:併發的任務屬於IO密集型 進程池: 線程池: 9.112 基於多線程實現併發的套接字通信(使用線程池) 服務端: f ...
  • del是個語句而不是方法 del member[1]:通過下標進行刪除 del member:也可刪除整個列表 remove():根據列表元素本身來刪除,而不是通過下標 member.remove('天天') pop(): member.pop():預設拋出列表最後一個元素, name = memb ...
  • 問題描述 近來,跳一跳這款小游戲風靡全國,受到不少玩家的喜愛。 簡化後的跳一跳規則如下:玩家每次從當前方塊跳到下一個方塊,如果沒有跳到下一個方塊上則游戲結束。 如果跳到了方塊上,但沒有跳到方塊的中心則獲得1分;跳到方塊中心時,若上一次的得分為1分或這是本局游戲的第一次跳躍則此次得分為2分,否則此次得 ...
  • JVM中優化指南 如何將新對象預留在年輕代 如何讓大對象進入年老代 如何設置對象進入年老代的年齡 穩定的 Java 堆 VS 動蕩的 Java 堆 增大吞吐量提升系統性能 嘗試使用大的記憶體分頁 使用非占有的垃圾回收器 Java虛擬機有自己完善的硬體架構,如處理器、堆棧、寄存器等,還具有相應的指令系統 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...