我的創作紀念日——多線程進階分享

多線程-進階

1. 鎖的策略

1.1 樂觀鎖&悲觀鎖

  1. 樂觀鎖

    • 預計在線程中數據大概率不會被其他線程拿去修改

    • 對于加鎖所作的準備較少。只有當修改的操作真正發生了,才會進行加鎖操作

    所以樂觀鎖適用于多讀少寫的情況,可以降低加鎖頻率,提升效率。

  2. 悲觀鎖

    • 預計在線程中數據大概率會被其他線程拿走做修改操作
    • 加鎖前的準備工作比較多

    所以悲觀鎖適用于對于線程安全要求高的場景。

1.2 輕量級鎖&重量級鎖

  1. 輕量級鎖

    • 對應于樂觀鎖
    • 加鎖前的操作占用的資源少,造成的阻塞情況少
    • 較少進行內核態和用戶態的切換
    • 較少進程之間的調度
  2. 重量級鎖

    • 對應于悲觀鎖
    • 加鎖前的準備工作多,容易造成線程阻塞
    • 大量內核態和用戶態的切換
    • 易引發進程之間的調度

    樂觀和悲觀是對于未加鎖結果的一種猜想

    重量輕量是對于加鎖后資源消耗的一種評價

    從這個角度說,這兩組概念都是在描述加鎖的工作對于資源的消耗。樂觀就是消耗的資源較少,悲觀反之。

1.3 自旋鎖&掛起等待鎖

  1. 自旋鎖

    • 重復、快速地進行鎖的獲取(稱為自旋“

    • 會增大cpu的消耗,可能會造成“忙等”

    • 適用于樂觀、輕量的策略(雖然一直不停地在獲取鎖,但是過不了多久就能夠真正獲取到鎖)

    • 偽代碼:

      while(搶鎖失敗) {加鎖...
      }
      

    優點:

    1. 在鎖競爭平緩的情況下能夠降低資源消耗,加快運行速度,避免線程之間因為簡單的任務而阻塞。

    2. 其他線程一旦把鎖釋放,就會第一時間拿到鎖

    缺點:

    1. 如果遇到鎖競爭激烈的情況,會有其他線程競爭不到鎖的情況。
    2. 競爭不到鎖,但是還會一直進行獲取鎖,造成cpu的資源浪費
  2. 掛起等待鎖

    • 遇到鎖競爭的情況就掛起等待

    • 適用于鎖競爭激烈的情況

    • 與悲觀、重量相對應

    • 偽代碼:

      while(搶鎖失敗) {wait();
      }
      

    優點:

    避免了cpu資源浪費

    缺點:
    不能第一時間搶到鎖,什么時候能加鎖,由系統決定

1.4 普通互斥鎖&讀寫鎖

  1. 普通互斥鎖

    • 類似于synchronized,在一個線程對于同一個對象進行加鎖的時候另一個線程不能對于這個對象的鎖進行獲取
  2. 讀寫鎖

    • 讀鎖

      • 給讀操作加鎖,讀的過程中其他線程只能再讀,不能寫

      讀的操作并沒有線程安全問題,但是只局限于讀,如果讀的過程中,把正在讀取的值拿走進行修改,那么就會產生讀到“臟值”的情況。

    • 寫鎖

      • 給寫操作加鎖,在一個線程寫的時候其他線程不能讀,也不能寫

      寫就是修改,修改就會有線程安全問題,如果在修改的過程中讀就可能會讀到“臟值”,如果在修改的過程中繼續修改就可能會引起數據的混亂。

1.5 公平鎖&非公平鎖

  1. 公平鎖

    • 基于前人發明的、前人規定的規則,“先來后到”就是公平
    • “先來后到”:一個線程擁有鎖的時間越長,那就越應該下一個獲取到鎖
    • 有效避免了線程餓死
  2. 非公平鎖

    • 在釋放鎖之后,隨機等概率競爭鎖的情況
    • mutex鎖就是非公平鎖,synchronized是封裝mutex的鎖,故而也是非公平鎖

    這里的“公平”和“非公平”只是基于前人發明的角度上,其實,在“等概率競爭鎖”的情況下也是一種“公平“。

1.6 可重入鎖&不可重入鎖

  1. 可重入鎖

    • 可以重復加鎖

    • synchronized就是可重入鎖

    • 偽代碼:

      lock();// 第一次加鎖之后繼續加鎖
      lock();// 第二次加鎖>
      
  2. 不可重入鎖

    • 不可重復加鎖
    • c++中的mutex就是不可重入鎖
    • 不可重入鎖在進行重復加鎖的時候會出現死鎖現象

結論:

  1. 樂觀鎖-輕量級鎖-自旋鎖都是對應的

  2. 悲觀鎖-重量級鎖-掛起等待鎖是對應的

  3. 樂觀:認為自己的家不會被偷,那就在家被偷或者被賊盯上的時候再去給門上鎖

    悲觀:認為自己的家已經被賊盯上了,一直上著鎖

  4. 輕量級鎖:只給家門上一個容易打開的鎖,開鎖的時候消耗自己的時間精力也會較少,樂觀地認為賊不會偷有鎖的家

    重量級鎖:給家門上一個不容易打開的防盜鎖,,開鎖的時候消耗自己的時間精力會增加,悲觀地認為一直有賊盯上我的房子

  5. 自旋鎖: 樂觀地認為沒有多少賊盯上了自己的家,所以每天都去看看自己的家有沒有被偷,優點是家一被偷就能夠知道,就能立馬上鎖,缺點是費時費力

    掛起等待鎖:悲觀地認為有很多賊已經盯上了自己的家,所以上一把不容易打開的鎖,當有人敲門的時候再去檢查是誰來,如果是賊那就不開門,讓鎖掛起等待,自己繼續在家里玩游戲。好處是減少了自己的任務量,可以有效防止多個賊同時頂上自己家的情況,缺點是這把鎖自己也不容易打開

  6. 普通互斥鎖:

2. CAS

樂觀鎖的常用實現算法是CAS算法,全稱是CompareAndSwap(比較和交換),這個也是cpu中存在的一條cas指令。

偽代碼:

boolen cas(address, expectedValue, swapValue) {if (&address == expectedValue) {&address = swapValue;return true;}return false;
}

address表示內存,expectedValue和swapValue是兩個寄存器。

如果內存中的值與交換前所期望的值相等,那就與準備交換的值進行交換(說是交換,其實就是賦值)。

expectedValue就是內存中值的”舊值“,通過與這個”舊值“進行比較,就能夠發現此值在修改前是否進行了改動,防止讀到”臟值“。


3. synchronized 原理

3.1 鎖的升級

synchronized鎖是一把自適應鎖,當一個線程執行到synchronized的時候,如果這個線程還未加上鎖,那么synchronized就會經理以下過程:

  1. 偏向鎖階段

    核心思想就是”懶漢模式”,非必要不加鎖(升級成輕量級鎖就是必要的時候)。

    • 在這個階段并不會真正地加上鎖,但是會對于有加鎖可能性的對象在對象頭進行標記,標記這個對象屬于哪個線程
    • 如果后續沒有線程競爭這把鎖,那就不再真正的進行加鎖,就省下了加鎖的消耗
    • 如果一旦發生線程安全問題,立馬升級為輕量級鎖

    感覺有點像占著茅坑不拉屎,一旦有人來了就蹲下拉屎,沒人來也就占著。(學校的占座不就是這樣嗎🐶)

  2. 輕量級鎖階段

    • 隨著線程之間少量的鎖競爭,偏向鎖狀態被消除(并不是解鎖了),進入輕量級鎖階段,這個階段由自旋鎖進行實現

    • synchronized內部會統計當前這個鎖對象有多少個線程想要競爭,如果數量多,那么還會升級為重量級鎖

      因為鎖的競爭大的話,對于自旋鎖來說大量的線程都在自旋,這樣不能提高效率,反而會帶來更多的cpu消耗。

  3. 重量級鎖階段

    • 隨著線程之間大量的鎖競爭,輕量級鎖升級為重量級鎖
    • 此時不會對于鎖競爭發生自旋,而是進入阻塞等待狀態,就會有大部分的線程讓出cpu
    • 當目前占有鎖的線程執行完畢以后就會釋放鎖,剩余線程等概率競爭鎖

3.2 鎖的工作原理

  1. synchronized鎖是:

    1. 對于以下鎖的狀態自適應:
      1. 樂觀、悲觀鎖
      2. 自旋、掛起等待鎖
      3. 輕量級、重量級
    2. 不是讀寫鎖
    3. 非公平鎖
    4. 可重入鎖
  2. 系統原生mutex鎖是:

    1. 悲觀鎖
    2. 重量級鎖
    3. 掛起等待鎖
    4. 互斥鎖
    5. 非公平鎖
    6. 不可重入鎖

    4. 其他的優化操作

    4.1 鎖消除

    當編譯器發現加鎖的這一部分代碼中,并未涉及變量修改的部分,那么就會自動將鎖去掉。

    使用線程安全的StringBuffer舉個例子:

    StringBuffer sb = new StringBuffer();
    sb.append("a");
    sb.append("b");
    sb.append("c");
    sb.append("d");
    

    這段代碼中雖然每個append 都有加鎖解鎖的操作,但是jvm+編譯器都發現這段代碼并沒有真正的線程安全問題,所以就不會進行加鎖和解鎖,避免了資源的無謂消耗。

    4.2 鎖粗化

    與鎖粗化關系密切的一個重要概念是鎖的粒度。

    鎖的粒度就像吃的牛肉粒一樣,每次加一個鎖就是一個牛肉粒,有兩種策略吃:

    1. 每次剝開一個牛肉粒就吃一個
    2. 每次剝開不吃,等到攢成一個大牛肉粒再吃

    兩種偽代碼分別對應這兩種情況:

    for() {synchronized(locker) {// TODO}
    }
    

    將鎖粗化:

    synchronized(locker) {for() {// TODO}
    }
    

4. 死鎖

4.1 死鎖的成因有4個:

  1. 循環等待

    當鎖A喚醒需要鎖B先釋放,鎖B喚醒需要鎖A先釋放,就構成了循環等待。

  2. 請求和保持

    當一個線程獲取新鎖的同時,它會繼續對于當前已有的鎖進行占有。

  3. 互斥使用

    資源被一個線程占有時,其他的線程不能同時使用。

  4. 不可搶占

    資源被一個線程占有時,其他線程不能搶占使用,只能等待當前占有者主動釋放。


其中,3和4是鎖的特性無法改變,但是1和2可以通過代碼結構進行破壞。

比較著名的是“哲學家就餐問題”:
在這里插入圖片描述

當這5名哲學家都需要吃飯的時候,餐桌上擁有的筷子數量肯定是不夠用的,但是如果給哲學家加上拿筷子的順序,那么就只會有一名哲學家在“阻塞等待”。

比如:規定每名哲學家只能先拿起編號小的筷子,那么就是0號老鐵先吃
在這里插入圖片描述

1號老鐵進行阻塞等待,等到0號老鐵吃完以后再先拿起編號小的1號筷子+2號筷子吃

在這里插入圖片描述

2,3,4老鐵同理。

4.2 破壞死鎖

就是調整代碼結構,破除循環等待這個條件。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/207195.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/207195.shtml
英文地址,請注明出處:http://en.pswp.cn/news/207195.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++初學教程四

一、程序設計 程序設計的三種基本結構:順序、選擇、循環 選擇結構(也叫分支結構) :判斷所指定的條件是否滿足,決定從給定的兩組或多組操作選擇其中的一種。 計算機的判斷是通過對表達式的計算來實現,也就是關系運算、邏輯運算。 用語句來體現就是if語句和switch語句。 一…

繼承與派生(2)

1.派生類的權限:派生類的成員函數可以訪問基類的public和protected類型的成員,而派生類的對象只能訪問public類型的成員 2.創建順序(先創造后析構):基類函數,派生類函數,組合類函數 類的組合按…

每日一練 | 華為認證真題練習Day145

1、一臺路由器通過RIP、OSPF和靜態路由都學習到了到達同一目的地址的路由。默認情況下,VRP將最終選擇通過哪種協議學習到的路由? A. 三種協議學習到的路由都選擇 B. 靜態路由 C. OSPF D. RIP 2、如果網絡管理員沒有配置骨干區域,則路由器…

VUE+THREE.JS 點擊模型相機緩入查看模型相關信息

點擊模型相機緩入查看模型相關信息 1.引入2.初始化CSS3DRenderer3.animate 加入一直執行渲染4.點擊事件4.1 初始化renderer時加入監聽事件4.2 觸發點擊事件 5. 關鍵代碼分析5.1 移除模型5.2 創建模型上方的彈框5.3 相機緩入動畫5.4 動畫執行 1.引入 引入模型所要呈現的3DSprite…

Dexie 查詢sql速度優化

Dexie查詢速度慢的原因主要一個優化點是復雜查詢下的count執行。 以下摘自Dexie官方文檔:https://dexie.org/docs/Collection/Collection.count() If executed on simple queries, the native IndexedDB ObjectStore count() method will be called (fast execution…

對標Gen-2!Meta發布新模型,進軍文生視頻賽道

隨著擴散模型的飛速發展,誕生了Midjourney、DALLE 3、Stable Difusion等一大批出色的文生圖模型。但在文生視頻領域卻進步緩慢,因為文生視頻多數采用逐幀生成的方式,這類自回歸方法運算效率低下、成本高。 即便使用先生成關鍵幀,再生成中間幀新方法。如…

Flink Window中典型的增量聚合(ReduceFunction / AggregateFunction)

一、什么是增量聚合函數 在Flink Window中定義了窗口分配器,我們只是知道了數據屬于哪個窗口,可以將數據收集起來了;至于收集起來到底要做什么,其實還完全沒有頭緒,這也就是窗口函數所需要做的事情。所以在窗口分配器…

聽GPT 講Rust源代碼--src/tools(9)

File: rust/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs 在Rust源代碼中,apply_demorgan.rs文件位于rust-analyzer工具的ide-assists庫中,其作用是實現一個輔助函數,用于在代碼中應用De Morgan定律的變換。 …

Android : 籃球記分器app _簡單應用

示例圖: 1.導包 在build.gradle 中 加入 // 使用androidx版本庫implementation androidx.lifecycle:lifecycle-extensions:2.1.0-alpha03 2. 開啟dataBinding android{...// 步驟1.開啟data bindingdataBinding {enabled true}...} 3.寫個類繼承 ViewModel pac…

整數與IP地址間的轉換

原理:ip地址的每段可以看成是一個0-255的整數,把每段拆分成一個二進制形式組合起來,然后把這個二進制數轉變成一個長整數。 舉例:一個ip地址為10.0.3.193 每段數字相對應的二進制數 10 00001010 0 00000000 3 00000011 193 110000…

自下而上-存儲全棧(TiDB/RockDB/SPDK/fuse/ceph/NVMe/ext4)存儲技術專家成長路線

數字化時代的到來帶來了大規模數據的產生,各行各業都面臨著數據爆炸的挑戰。 隨著云計算、物聯網、人工智能等新興技術的發展,對存儲技術的需求也越來越多樣化。不同應用場景對存儲的容量、性能、可靠性和成本等方面都有不同的要求。具備存儲技術知識和技…

機器學習-聚類問題

前言 聚類算法又叫做”無監督分類“,目標是通過對無標記訓練樣本來揭示數據的內在性質及 規律,為進一步的數據分析提供基礎。 Kmeans 作為聚類算法的典型代表,Kmeans可以說是最簡單的聚類算法,沒有之一,那她是怎么完…

MySQL為何偏愛B+樹索引

一、MySQL、B樹概念 MySQL是一種關系型數據庫,它使用SQL語言來操作數據。SQL語言可以實現對數據的增刪改查等操作,但是如果數據量很大,那么這些操作的效率就會很低。為了提高效率,MySQL引入了索引的概念。 索引是一種數據結構&am…

人體關鍵點檢測1:人體姿勢估計數據集

人體關鍵點檢測1:人體姿勢估計數據集 目錄 人體關鍵點檢測1:人體姿勢估計數據集 1.人體姿態估計 2.人體姿勢估計數據集 (1)COCO數據集 (2)MPII數據集 (3)Human3.6M &#xf…

PostgreSQL 主鍵和唯一鍵的區別

主鍵和唯一鍵的區別 主鍵(Primary Key): 主鍵是用于唯一標識表中的每一條記錄的鍵。主鍵必須是唯一的,不允許為空。一個表只能有一個主鍵。主鍵可以由一個或多個字段組成。主鍵的值在整個表中必須是唯一的,用于確保數據…

編譯器:swc 究竟比 babel 快在哪里?

前言 swc 與 babel 都是 JavaScript 編譯器,它們的主要功能是將 ES2015 以及 TypeScript, Flow, JSX 等語法轉換為瀏覽器或環境中的向后兼容的 JavaScript 代碼。 哪里快了? 1. 開發語言的優勢 swc 是用 Rust 語言開發的,而 babel 是用 Java…

MS5228/5248/5268:2.7V 到 5.5V、 12/14/16Bit、內置基準、八通道數模轉換器

MS5228/MS5248/MS5268 是一款 12/14/16bit 八通道輸出的電壓型 DAC ,內部集成上電復位電路、可選內部基準、接口采用四線串口模式, 最高工作頻率可以到 40MHz ,可以兼容 SPI 、 QSPI 、 DSP 接口和 Microwire 串口。輸出接到一個 …

IP地址/16或者/24的意義

IP地址/16或者/24的意義 2023-04-26 16:54 獵手家園 閱讀(533) 評論(0) 編輯 收藏 舉報 當創建VPC專有網絡時,許多人會遇到填寫IPv4地址的情況,通常使用的格式是xxx.xxx.xxx.xxx/16或者xxx.xxx.xxx.xxx/24。那么這個斜杠后面的數字代表什么意思呢&#…

<習題集><LeetCode><鏈表><2/19/21/23/24>

目錄 2. 兩數相加 19. 刪除鏈表的倒數第 N 個結點 21. 合并兩個有序鏈表 23. 合并 K 個升序鏈表 24. 兩兩交換鏈表中的節點 2. 兩數相加 https://leetcode.cn/problems/add-two-numbers/ public ListNode addTwoNumbers(ListNode l1, ListNode l2) {//head是cur鏈表頭節點…

pdf轉png的兩種方法

背景:pdf在一般公司,沒有辦公系統,又不是word/wps/Office系統,讀不出來,識別不了,只能將其轉化為圖片png,因此在小公司或者一般公司就需要pdf轉png的功能。本文將詳細展開。 1、fitz庫(也就是PyMuPDF) 直接pip安裝PyMuPDF即可使用,直接使用fitz操作,無需其他庫。 …