淺析AQS

来源:https://www.cnblogs.com/hexiayuliang666/archive/2022/04/17/16156939.html
-Advertisement-
Play Games

阻塞隊列 同步隊列 AQS 原理 數據結構 CLH 共用方式 設計模式 組件 自定義 ...


1、AbstractQueue抽象隊列、BlockingQueue阻塞隊列、Deque雙端隊列

2、Queue FIFO先進先出,

      寫入:隊列滿阻塞等待,取出:隊列滿阻塞等待生產

3、使用場景:多線程併發處理、線程池

4、阻塞隊列(BlockingQueue)——四組API(添加、移除、判斷隊列首部的場景)

==1、會拋出異常

==2、有返回值,不拋出異常

==3、阻塞等待(一直阻塞等待)

==4、超時等待

=========拋出異常 add()\remove()\element()=========

//拋出異常
   public static void atest(){
       //數組類型的阻塞隊列 要指定 隊列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
       //添加操作 返回Boolean值
       System.out.println(blockingQueue.add("a"));
       System.out.println(blockingQueue.add("b"));
       System.out.println(blockingQueue.add("c"));
       /*
       java.lang.IllegalStateException: Queue full
       隊列已滿 拋出異常
        */
//       System.out.println(blockingQueue.add("d"));
       //檢測隊首元素
       System.out.println("隊首元素"+blockingQueue.element());
       //移除操作 返回移除的添加的元素
       System.out.println(blockingQueue.remove());
       System.out.println(blockingQueue.remove());
       System.out.println(blockingQueue.remove());
       /*
       java.util.NoSuchElementException
       元素為空 不能再移除 拋出異常
        */
//       System.out.println(blockingQueue.remove());
  }    public static void atest(){
       //數組類型的阻塞隊列 要指定 隊列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
       //添加操作 返回Boolean值
       System.out.println(blockingQueue.add("a"));
       System.out.println(blockingQueue.add("b"));
       System.out.println(blockingQueue.add("c"));
       /*
       java.lang.IllegalStateException: Queue full
       隊列已滿 拋出異常
        */
//       System.out.println(blockingQueue.add("d"));

       //移除操作 返回移除的添加的元素
       System.out.println(blockingQueue.remove());
       System.out.println(blockingQueue.remove());
       System.out.println(blockingQueue.remove());
       /*
       java.util.NoSuchElementException
       元素為空 不能再移除 拋出異常
        */
//       System.out.println(blockingQueue.remove());
  }

=========有返回值,不拋出異常 add()\remove()\element()=========

//  不拋出異常
   public static void btest(){
       ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
       //添加 返回Boolean值 添加成功 返回true
       System.out.println(blockingQueue.offer("a"));
       System.out.println(blockingQueue.offer("b"));
       System.out.println(blockingQueue.offer("c"));
       // 隊列大小為3   繼續添加元素   不能繼續添加 返回false
       System.out.println(blockingQueue.offer("d"));

       //移除 返回元素
       System.out.println(blockingQueue.poll());
       System.out.println(blockingQueue.poll());
       System.out.println(blockingQueue.poll());
       // 隊列在元素全部移除後 繼續移除操作   不能繼續移除 返回null
       System.out.println(blockingQueue.poll());
  }

=========阻塞等待(一直阻塞等待) add()\remove()\element()=========

//    一直阻塞等待    public static void ctest() throws InterruptedException {        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);        //添加元素        blockingQueue.put("a");        blockingQueue.put("b");        blockingQueue.put("c");        // 隊列已滿  如果繼續添加元素  則造成阻塞  一直阻塞等待  程式一直在等//        blockingQueue.put("d");        //移除元素        System.out.println(blockingQueue.take());        System.out.println(blockingQueue.take());        System.out.println(blockingQueue.take());        //隊列為空   如果繼續移除元素  則造成阻塞  一直在等待元素來移除  程式一直在等//        System.out.println(blockingQueue.take());•    }

=========超時等待 add()\remove()\element()=========

//    等待超時退出
   public static void dtest() throws InterruptedException {
       ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
       //添加元素 offer(元素,時間,單位) offer方法的重載方法
       System.out.println(blockingQueue.offer("a"));
       System.out.println(blockingQueue.offer("b"));
       System.out.println(blockingQueue.offer("c"));
       //隊列已滿 繼續添加元素 等待超過2秒就結束程式
       System.out.println(blockingQueue.offer("d", 2, TimeUnit.SECONDS));

       //移除元素 poll(時間,單位) poll方法的重載方法
       System.out.println(blockingQueue.poll());
       System.out.println(blockingQueue.poll());
       System.out.println(blockingQueue.poll());
       //隊列為空 繼續移除元素 等待超過2秒就結束程式
       System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
  }

5、同步隊列

==沒有容量(即不長時間存儲元素)

    進去一個元素,必須等待取出(take())後才能再往裡邊繼續添加(put())

==例子:

    BlockingQueue<String> blockingQueue = new SynchronousQueue<String>();//同步隊列
   //添加元素 線程
   new Thread(()->{
       try {
           System.out.println(Thread.currentThread().getName()+"put 1");
           blockingQueue.put("1");
           System.out.println(Thread.currentThread().getName()+"put 2");
           blockingQueue.put("2");
           System.out.println(Thread.currentThread().getName()+"put 3");
           blockingQueue.put("3");
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
  },"線程1").start();

   //移除元素 線程
   new Thread(()->{
       try {
           TimeUnit.SECONDS.sleep(2);
           System.out.println(Thread.currentThread().getName()+"==>"+blockingQueue.take());
           TimeUnit.SECONDS.sleep(2);
           System.out.println(Thread.currentThread().getName()+"==>"+blockingQueue.take());
           TimeUnit.SECONDS.sleep(2);
           System.out.println(Thread.currentThread().getName()+"==>"+blockingQueue.take());
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
  },"線程2").start();
}

 

AQS

1、一個用來構建鎖和同步器的框架,定義了鎖的實現機制,並開放出擴展的地方,讓子類去實現。(封裝得很好,但又有子類擴展的地方)

例如:lock(加鎖)時鎖的內部機制:

      使用鎖Lock時,AQS開放state欄位,然子類可以根據state欄位來決定是否能夠獲得鎖,對於獲取不到鎖的線程,AQS會自動進行管理,無需子類鎖關心。

2、使用AQS能夠簡單且高效地構造出大量應用廣泛的同步器:ReentrantLock,Semaphore,ReentrantReadWriteLock,SynchronousQueue,FutureTask等等,都基於AQS。也可以自定義同步器。

3、AQS底層:

==(1)由 同步隊列 + 條件隊列 聯合實現(CLH隊列鎖)

====一般情況下有同步隊列(雙向鏈表)組成,條件隊列(單向鏈表)不是必須存在的,當程式中存在condition時,才會存在此列表。

====同步隊列管理獲取不到鎖的線程的排隊和釋放

====條件隊列是在一定場景下,對同步隊列的補充(非必須的),如,獲得鎖的線程從空隊列中拿數據(隊列是空的,拿不到數據的),此時,條件隊列會管理該線程,使線程阻塞。

==(2)核心思想:AQS內部維護一個CLH隊列來管理。

====線程請求共用資源時,

     如果被請求的共用資源空閑,則將當前請求資源的線程設置為有效線程,並且將共用資源設置為鎖定狀態;

     如果被請求的共用資源被占用,則需要CLH隊列鎖實現的機制來實現線程阻塞等待以及線程被喚醒使鎖的分配:即將暫時獲取不到鎖的線程加入到隊列中。(上邊的同步隊列)

==(3)CLH鎖隊列:

====是一個虛擬的雙向隊列(不存在隊列實例,僅存在節點間的關聯關係)

====AQS是將每條請求共用資源的線程封裝成一個CLH鎖隊列的一個個節點(Node)實現鎖的分配。

==(4)AQS中同步隊列的工作流程和數據結構:(結合兩圖理解)

 

 

====工作過程:

(1)當前線程獲取同步狀態失敗,同步器會將當線程及等待狀態等信息構成一個Node節點,加入CLH隊列中,放在隊尾,同步器重新設置尾節點。

(2)加入隊列後,會阻塞當前線程

(3)同步狀態被釋放並且同步器重新設置首節點,同步器喚醒等待隊列中第一個節點,讓其再次獲取同步狀態。

====AQS使用一個int成員變數來表示同步狀態,通過內置的FIFO隊列來完成獲取資源線程的排隊工作。

====AQS使用CAS對該同步狀態進行原子性操作實現對其值的修改

private volatile int state;//共用變數,使用volatile修飾保證線程可見性

====狀態信息通過protected類型的getState,setState,compareAndSetState進行操作

//返回同步狀態的當前值
protected final int getState() {
   return state;
}
//設置同步狀態的值
protected final void setState(int newState) {
   state = newState;
}
//原子地(CAS操作)將同步狀態值設置為給定值update如果當前同步狀態的值等於expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
   return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

4、AQS對資源的共用方式

定義了兩種資源共用方式:

==(1)Exclusive(獨占):又分為公平鎖和非公平鎖

       只有一個線程能執行,如ReentrantLock

====公平鎖:按照線程在隊列中的排隊順序, 先到者先拿到鎖

====非公平鎖:當前線程要獲取鎖時,無視隊列內的順序,直接去強鎖,誰搶到了就是誰的。

==(2)Share(共用):

       多個線程可以同時執行,如Semaphore\CyclicBarrier\ReadWriteLock\CountDownLatch

==(3)ReentrantReadWriteLock:組合兩種資源共用方式的

====讀鎖運行多個線程同時執行對同一資源進行讀操作

====寫鎖僅允許一個線程能執行對同一資源進行寫操作

==(4)不同自定義的同步器有不同的共用資源方式,自定義同步器在實現時,只需要實現共用資源state的獲取與釋放方式即可。

 

5、AQS使用的設計模式

(1)基於模板設計模式的

(2)自定義同步器的方式:

==1、繼承AbstractQueuedSynchronizer並重寫指定的方法

        重寫方法是指對於共用資源state的獲取和釋放

==2、將AQS組合在自定義同步組件的實現中,並調用其模板方法

       這些模板方法會調用上邊繼承重寫的方法

====AQS提高的模板方法

protected boolean tryAcquire(int)//獨占方式。嘗試獲取資源,成功則返回true,失敗則返回false。
protected boolean tryRelease(int)//獨占方式。嘗試釋放資源,成功則返回true,失敗則返回false。
protected int tryAcquireShared(int)//共用方式。嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源。
protected boolean tryReleaseShared(int)//共用方式。嘗試釋放資源,成功則返回true,失敗則返回false。
protected boolean isHeldExclusively()//該線程是否正在獨占資源。只有用到condition才需要去實現它。

====除以上的方法外,AQS類中其它方法不能被重寫,都為final

====例:

=======獨占方式=======

ReentrantLock

(1)state初始化為0,表示未鎖定狀態;

(2)A線程lock()時,會調用tryAcquire()獨占該鎖並state+1;

(3)其它線程tryAcquire()時會失敗,直到A線程unlock()到state=0,即釋放,其它線程才能獲取該鎖。

(4)A線程釋放前,A自己時可以重覆獲取此鎖的(state++),即可重入

(5)A線程釋放,一定要將state回歸為state=0

=======共用方式=======

CountDownLatch,任務分為N個子線程進行執行

(1)state初始化為N(與線程數一致)

(2)N個子線程並行每執行countDown()一次,state CAS減一

(3)所有子線程都執行完成後即state=0,會unpark()主調用線程

(4)主調用線程從await()函數返回,繼續後續的操作

 

6、AQS組件

(1)Semaphore(信號量):允許多個線程同時訪問

====synchronized和ReentrantLock是一次只允許一個線程訪問某個資源

(2)CountDownLatch(倒計時器):同步工具類,用來協調多個線程間的同步,通常用來控制線程等待,可以讓某個線程等待直到倒計時結束,再開始執行。

(3)CyclicBarrier(迴圈柵欄):可以實現線程間的技術等待,功能更強大複雜。

== Cyclic(可迴圈使用)的Barrier(屏障):

====讓一組線程到達一個屏障(同步點)時被阻塞,直到最後一個線程到達屏障(同步點)時,才會打開屏障,所有被攔截的線程此時才會繼續執行。

====CyclicBarrier的預設構造方法:CyclicBarrier(int parties)

=======parties參數:表示屏障攔截的線程數量

=======每個線程調用await()方法告知CyclicBarrier已到達屏障,然後被阻塞。


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

-Advertisement-
Play Games
更多相關文章
  • 函數是帶名字的代碼塊,要執行函數定義的特定任務,可調用該函數。 需要在程式中多次執行同一項任務時,你無需反覆編寫完成該任務的代碼,而只需調用執行該任務的函數,通過使用函數,程式的編寫、閱讀、測試和修複都將更容易。主程式文件的組織更為有序 一、如何定義一個函數 使用關鍵字 def 來定義一個函數。 d ...
  • 簡介 通過annotation像強類型language那樣指定變數類型,包括參數和返回值的類型 因為Python是弱類型語言,這種指定實際上無效的。所以這種寫法叫annotation,就是個註釋參考的作用。通過annotation可以極大的提升代碼可讀性 語法為“var_name: type [= ...
  • 本文將以單鏈表和靜態鏈表的初始化代碼(c++)為例,具體分析了結構體中typedef struct LNode{....} LNode, *LinkList, SLinkList[MaxSize];的相關問題,並補充了C++中引用類型的一點知識。 ...
  • https://www.cnblogs.com/yeungchie/ 記錄一些常用的 模塊 / 方法 。 多線程 使用模塊 threads use 5.010; use threads; # 定義一個需要併發的子函數 sub func { my $id = shift; sleep 1; print ...
  • 測評目錄 python技能樹測評 python技能樹是什麼 python技能樹長什麼樣 如何學習python技能樹 python技能樹可能需要的改進 對python技能樹的總結 CSDN MarkDown編輯器測評 python技能樹測評 CSDN上線了一個叫python技能樹的產品,主要是用來作為 ...
  • 線程通信 1、場景:生產者和消費者問題 ==倉庫、生產者、消費者 倉庫只能存放一個產品,生產者將生產出來的產品放入倉庫,消費者將倉庫中產品取走 如果倉庫中沒有產品,生產者將產品放入倉庫,否則停止生產並等待(阻塞),直到倉庫中的產品被消費者取走 如果倉庫中有產品,消費者可以將產品取走消費,否則停止消費 ...
  • 原文鏈接:http://www.zhoubotong.site/post/37.html 如果使用go語言自帶的json庫,使用的是反射,而go語言中反射性能較低。easyjson就是一個比較好的替代方案。 esayjson安裝(https://gitcode.net/mirrors/mailru/ ...
  • 分散式基礎理論 1. 什麼是分散式系統? 分散式系統是若幹獨立電腦的集合,這些電腦對於用戶來說就像單個系統 2. 應用架構演變 單一應用架構 當網站流量很小時,只需一個應用,將所有功能都部署在一起,以減少部署節點和成本,適用於小型網站,小型管理系統 垂直應用架構 當訪問量逐漸增大,單一應用增加機 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...