【談一談】并發編程_鎖的分類

【談一談】并發編程_鎖的分類

Hello!~大家好!~每天進步一點點,日復一日,我們終將問劍頂峰

這里主要是介紹下我們常用的鎖可以分為幾類,目的是整體框架作用~方便后續的并發文章

說白了,這篇就是開頭哈~

在這里插入圖片描述

本文總綱:

在這里插入圖片描述

一.可重入鎖和不可重入鎖

我們開發中一般用到的都是可重入鎖比如Synchronized,ReentrantReadWriteLock,ReentrantLock都是可重入的

不可重入的鎖很少見,也不怎么用到(如果要用,一般都自己通過Lock定義實現)

1.可重入鎖:

它也稱為遞歸鎖,啥意思呢?

  • 是指同一線程在獲取鎖(如A鎖)之后,可以再次對該鎖(A)進行獲取,而不會造成死鎖。
  • 這種鎖支持同一個線程對資源的重復加鎖,并且在釋放鎖時,必須是獲取鎖的次數與釋放鎖的次數相等時,才會真正釋放鎖

還是有點迷糊嗎?我們舉個JavaReentrantLock實現可重入鎖的簡單例子來加深理解:

ReentrantLock鎖機制:(我先說下,不然看下面代碼,估計不怎么好理解哈)

  • 內部維護了一個計數器
  • 每當線程獲取鎖時,計數器加1;每當線程釋放鎖時,計數器減1
  • 只有當計數器為0時,其他線程才有機會獲取該鎖

下面代碼:

  • doSomething()方法調用了doSomethingElse()方法,由于ReentrantLock可重入特性,第二次調用lock()不會導致線程阻塞,而是使鎖計數器加1
  • 當對應的unlock()方法被調用兩次后,鎖才會真正釋放允許其他線程獲取鎖
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockExample {private final ReentrantLock lock = new ReentrantLock();public void doSomething() {lock.lock(); // 獲取鎖try {// 在這里執行臨界區代碼doSomethingElse();} finally {lock.unlock(); // 無論是否發生異常,最終都要釋放鎖}}private void doSomethingElse() {lock.lock(); // 同一線程內再次獲取鎖,不會阻塞try {// 在這里執行另一段臨界區代碼} finally {lock.unlock(); // 釋放鎖}}
}

2.不可重入鎖

是指一個線程獲取鎖后,在未釋放該鎖的情況下**,無法再次獲取**該鎖的同步機制,必須等此鎖釋放后,才能再獲取鎖

細心的同學可能會發現,在Java的標準庫中,并沒有直接提供不可重入鎖的實現,為什么??

  • 因為在多層級調用或遞歸場景下(大多數的并發場景中),它們很容易造成意外死鎖問題,
  • 而可重入鎖(如ReentrantLock)則可以安全地支持這些復雜情況。

舉個例子:(不考慮復雜場景哈~你別杠,我們只是演示下,目的是懂)

通過簡單的計數器模擬,當鎖被獲取時,增加計數器,有且僅有計數器為0是才允許獲取鎖

下面的代碼:

  • 這樣的鎖如果在線程內部遞歸調用lock方法,將會導致后續嘗試獲取鎖的操作阻塞,從而表現出不可重入的特性。
public class NonReentrantLock {private boolean isLocked = false;private Thread holdingThread;public synchronized void lock() throws InterruptedException {while (isLocked && holdingThread != Thread.currentThread()) {wait();}isLocked = true;holdingThread = Thread.currentThread();}public synchronized void unlock() {if (holdingThread == Thread.currentThread()) {isLocked = false;holdingThread = null;notifyAll();} else {throw new IllegalMonitorStateException("當前線程并未持有此鎖");}}
}

二.樂觀鎖和悲觀鎖

1.樂觀鎖 (Optimistic Locking)

  • 是一種在讀取數據時不會立即加鎖,而是在更新數據時才會檢查在此期間是否有其他事務對數據進行了修改的并發控制策略
  • 假設大多數情況下不會有沖突發生(很樂觀吧~哈哈哈,),因此在進行數據操作時保持樂觀態度

在補充下:

數據庫系統中,樂觀鎖通常通過版本號時間戳等機制實現

  • 當一個事務準備更新數據時,它會首先檢查該數據的版本號時間戳是否與最初讀取時一致
  • 如果一致: 則執行更新操作并更新版本號或時間戳;
  • 如果不一致,則表示在此期間有其他事務對該數據進行了修改,此時當前事務通常會選擇回滾以避免覆蓋其他事務的更改。

例子:

Hibernate中使用@Version注解:

  • 每次更新MyEntity實例時,Hibernate都會自動檢查并更新version字段,從而實現了樂觀鎖的效果

又如Java中提供的CAS操作,典型的樂觀鎖實現

再舉個實際點: 一個整數版本號來模擬樂觀鎖機制

transfer方法嘗試進行轉賬操作時,

  1. 首先記錄下當前賬戶的版本號。
  2. 然后模擬可能存在其他事務的情況,這里簡單地直接增加版本號以示意圖。
  3. 最后,在真正執行更新操作前,再次檢查版本號是否與最初讀取時一致。
  • 如果一致,則執行轉賬邏輯并遞增版本號;
  • 如果不一致,則表示存在并發沖突,轉賬操作失敗。

這樣就實現了一個基于版本號的樂觀鎖機制,它可以防止在并發環境下的數據不一致性問題。

public class Account {private int balance; // 賬戶余額private int version; // 數據版本號public Account(int initialBalance) {this.balance = initialBalance;this.version = 0;}// 使用樂觀鎖進行轉賬操作public boolean transfer(Account to, int amount) {// 保存當前賬戶和目標賬戶的原始版本號int originalVersion = this.version;// 模擬并發環境下可能出現的其他事務操作simulateOtherTransactions();// 嘗試更新賬戶余額和版本號if (this.version == originalVersion) {// 更新前檢查版本號未變if (this.balance >= amount) {this.balance -= amount;to.balance += amount;// 成功更新數據后,將版本號遞增this.version++;return true;} else {System.out.println("余額不足,轉賬失敗");}} else {System.out.println("并發沖突,有其他事務修改了賬戶數據,轉賬失敗");}return false;}// 模擬在轉賬操作過程中可能存在的其他事務對賬戶數據的修改private void simulateOtherTransactions() {// 這里僅用于演示,在實際應用中可能是由其他線程或事務引起的// 假設另一個事務在此時修改了賬戶數據并增加了版本號this.version++;}
}

2.悲觀鎖(Pessimistic Locking)

獲取不到鎖資源時,會將當前線程掛起(進入Blocked或者waitting)(有著一種生于憂患意識)

官方術語:

  • 是一種在訪問數據時假設會發生并發沖突,并立即對數據進行加鎖以防止其他事務或線程對其進行修改的并發控制策略。
  • 傾向于認為每次對數據的操作都可能引發并發問題,所以在獲取數據前就先鎖定資源

這種操作例子在數據庫層面經常能看見:

如: 當第一個事務執行SELECT ... FOR UPDATE時,

  • 會對當前的查詢記錄進行鎖定,此時其他任何事務試圖讀取或修改這條記錄都會被阻塞,直到第一個事務提交或回滾釋放鎖

如: 在Java應用層面,

  • JDBC中的java.sql.Connection提供的setAutoCommit(false)方法:
  • 可以開啟手動事務管理,配合數據庫的悲觀鎖機制實現更細粒度的并發控制。

再舉個: 以synchronized關鍵字為例,提供一個簡單的線程安全的銀行賬戶轉賬操作

  • transfer()方法通過synchronized關鍵字修飾,這意味著在同一時間只能有一個線程訪問這個方法。

  • 當一個線程調用transfer進行轉賬操作時,其他線程必須等待當前線程完成操作并釋放鎖后才能繼續執行。

    這就是悲觀鎖的應用,它假設并發環境下會存在數據沖突,并直接對資源進行鎖定,以防止多個線程同時修改共享資源導致的數據不一致問題。

public class BankAccount {private double balance;public BankAccount(double initialBalance) {this.balance = initialBalance;}// 使用synchronized實現悲觀鎖public synchronized void transfer(BankAccount to, double amount) throws InterruptedException {if (this.balance >= amount) {// 鎖定當前對象(即鎖定該方法),確保同一時間只有一個線程能執行此方法Thread.sleep(100); // 模擬耗時操作,如數據庫查詢或更新this.balance -= amount;to.balance += amount;System.out.println("From: " + Thread.currentThread().getName() + ", Transfer " + amount + " completed.");} else {throw new IllegalArgumentException("Insufficient balance.");}}public static void main(String[] args) {BankAccount accountA = new BankAccount(100);BankAccount accountB = new BankAccount(0);Thread thread1 = new Thread(() -> accountA.transfer(accountB, 50));Thread thread2 = new Thread(() -> accountA.transfer(accountB, 60));thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Final balances: A=" + accountA.balance + ", B=" + accountB.balance);}
}

三.公平鎖和非公平鎖

這當中的關鍵點就是: 是否正常排隊

1.公平鎖

  • 是一種線程調度策略,它保證了等待鎖的線程按照它們請求鎖的順序獲得鎖。
  • 公平鎖機制下,當鎖釋放時,會優先分配給已經在隊列中等待時間最長的線程,而不是隨機選擇一個等待的線程。

注意:

  • 在公平鎖環境下,如果有多個線程在等待獲取鎖,那么鎖會被分配給等待時間最久的那個線程,這種策略能夠減少"線程饑餓"(即某些線程長時間無法獲取到鎖)的問題,提高系統的整體公平性
  • 公平鎖雖然在理論上提供了更好的公平性,但可能會降低系統的整體吞吐量
    • 因為每次釋放鎖時都需要維護和檢查等待隊列,并且需要考慮線程上下文切換的成本。
    • 而在非公平鎖(默認情況下)中,獲取鎖的線程可能是最近剛剛嘗試獲取鎖的線程,這可能導致更高的并發性和系統性能,但可能也會導致某些線程長期得不到執行機會。

舉個例子:

假設我們有一個共享資源(一個計數器)需要多個線程安全地進行遞增操作:

ReentrantLock類:參數設為true,可以創建一個公平鎖

在這個例子中:

我們創建了一個公平鎖,并在一個共享的計數器上進行了遞增操作

  • 當多個線程同時調用increment()方法時,公平鎖會確保等待時間最長的線程優先獲得鎖并執行操作。

  • 由于每個線程在操作后都休眠了100毫秒,這有助于模擬實際的并發環境,使得不同線程之間的執行順序更易于觀察。

  • 公平鎖策略下,理論上線程獲取鎖的順序將盡可能按照它們請求鎖的時間順序進行,因此輸出的結果應能體現出相對有序的執行過程。

import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.IntStream;public class FairLockExample {private final ReentrantLock lock = new ReentrantLock(true); // 創建一個公平鎖private int counter = 0;public void increment() {lock.lock();try {counter++;System.out.println(Thread.currentThread().getName() + " incremented the counter to: " + counter);Thread.sleep(100); // 模擬耗時操作} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {lock.unlock();}}public static void main(String[] args) throws InterruptedException {FairLockExample example = new FairLockExample();IntStream.range(0, 10).forEach(i -> new Thread(() -> example.increment(), "Thread-" + i).start());Thread.sleep(5000); // 等待所有線程執行完成System.out.println("Final counter value: " + example.counter);}
}

2.非公平鎖

是一種線程調度策略,與公平鎖相反,

  • 在釋放鎖后并不保證等待時間最長的線程一定能獲得鎖
  • 當鎖可用時,非公平鎖可能會允許任何一個正在等待獲取鎖的線程獲取鎖,即使有其他線程已經等待了更長的時間。(適者生存,能者居之)

模擬場景說明:

線程A獲取到鎖資源,線程B沒有拿到,線程B去排隊,這時線程C跑來了,線程C咋么做呢?

  • 首先去嘗試競爭一波
  • 競爭成功: 拿到鎖,美滋滋進行執行
  • 競爭失敗: 沒有拿到鎖資源,老老實實的排到B的后面,直到B拿到鎖資源或者B取消后,才去競爭鎖資源

舉個例子:

使用非公平鎖實現多線程安全遞增操作的例子

在這個例子中

  1. 我們創建了一個非公平鎖,并在一個共享的計數器上進行了遞增操作
  2. 當多個線程同時調用increment()方法時,非公平鎖可能讓任何等待鎖的線程獲取到鎖,而不考慮它們等待的先后順序。
  3. 因此,輸出的結果可能顯示出線程獲取鎖和執行的相對無序性
  4. 雖然非公平鎖可能導致某些線程“饑餓”(長時間無法獲取鎖),但在某些情況下,它能提供更高的吞吐量,因為減少了線程上下文切換的成本和隊列維護的開銷
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.IntStream;public class NonFairLockExample {private final ReentrantLock lock = new ReentrantLock(); // 創建一個非公平鎖(默認false)private int counter = 0;public void increment() {lock.lock();try {counter++;System.out.println(Thread.currentThread().getName() + " incremented the counter to: " + counter);Thread.sleep(100); // 模擬耗時操作} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {lock.unlock();}}public static void main(String[] args) throws InterruptedException {NonFairLockExample example = new NonFairLockExample();IntStream.range(0, 10).forEach(i -> new Thread(() -> example.increment(), "Thread-" + i).start());Thread.sleep(5000); // 等待所有線程執行完成System.out.println("Final counter value: " + example.counter);}
}

完結撒花

在這里插入圖片描述

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

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

相關文章

Photoshop 2023:重塑創意,引領數字藝術新紀元

在數字藝術的浩瀚星空中,Adobe Photoshop 2023(簡稱PS 2023)如同一顆璀璨的新星,為Mac和Windows用戶帶來了前所未有的創意體驗。這款強大的圖像處理軟件不僅繼承了前作的精髓,更在細節上進行了諸多創新,讓每…

運行Python文件時出現‘utf-8’code can‘t decode byte 如何解決?(如圖)

如圖 亦或者出現“SyntaxError: Non-UTF-8 code starting with \xbb ” 出現這種問題往往是編碼格式導致的,我們可以在py文件中的第一行加入以下代碼: # codingutf-8或者 # codinggdk優先使用gbk編碼 解釋一下常用的兩種編碼格式: utf-…

朱維群將出席用碳不排碳碳中和頂層科技路線設計開發

演講嘉賓:朱維群 演講題目:“用碳不排碳”碳中和頂層科技路線設計開發 簡介 姓名:朱維群 性別:男 出生日期:1961-09-09 職稱:教授 1998年畢業于大連理工大學精細化工國家重點實驗室精細化工專業&…

什么是B+樹,和B樹有什么不同?

👉博主介紹: 博主從事應用安全和大數據領域,有8年研發經驗,5年面試官經驗,Java技術專家,WEB架構師,阿里云專家博主,華為云云享專家,51CTO 專家博主 ?? 個人社區&#x…

Spring Initializer環境問題

1.基于jdk8與本地 環境準備 1)下載jdk8并安裝 2&#xff09;下載maven 3.6.3并解壓放入D盤maven目錄下&#xff0c;去掉外層 設置阿里源 打開settings.xml,在mirrors標簽之內增加&#xff0c;注意粘貼后</id>中的/有可能被刪掉&#xff0c;要自己補上 <mirror>&l…

健身房預約小程序制作詳細步驟解析

如果你是一位健身愛好者&#xff0c;或者是一位健身教練&#xff0c;你一定知道預約健身的痛苦。傳統的預約方式不僅麻煩&#xff0c;而且效率低下。但是&#xff0c;現在&#xff0c;我們可以使用一種神仙工具——喬拓云網&#xff0c;來搭建一個屬于自己的健身預約小程序&…

【VTKExamples::PolyData】第四十三期 PolyDataPointSampler

很高興在雪易的CSDN遇見你 VTK技術愛好者 QQ:870202403 前言 本文分享VTK樣例PolyDataPointSampler,并解析接口vtkPolyDataPointSampler,希望對各位小伙伴有所幫助! 感謝各位小伙伴的點贊+關注,小易會繼續努力分享,一起進步! 你的點贊就是我的動力(^U^)ノ~YO …

如何使用 CrewAI 構建協作型 AI Agents

一、前言 AI Agents 的開發是當前軟件創新領域的熱點。隨著大語言模型 (LLM) 的不斷進步&#xff0c;預計 AI 智能體與現有軟件系統的融合將出現爆發式增長。借助 AI 智能體&#xff0c;我們可以通過一些簡單的語音或手勢命令&#xff0c;就能完成以往需要手動操作應用程序才能…

運維的利器–監控–zabbix–grafana

運維的利器–監控–zabbix–grafana 一、介紹 Grafana 是一個跨平臺的開源的度量分析和可視化工具 , 可以通過將采集的數據查詢然后可視化的展示 。zabbix可以作為數據源&#xff0c;為grafana提供數據&#xff0c;然后grafana將數據以圖表或者其他形式展示出來。zabbix和gra…

基于YOLOv的目標追蹤與無人機前端查看系統開發

一、背景與簡介 隨著無人機技術的快速發展&#xff0c;目標追蹤成為無人機應用中的重要功能之一。YOLOv作為一種高效的目標檢測算法&#xff0c;同樣適用于目標追蹤任務。通過集成YOLOv模型&#xff0c;我們可以構建一個無人機前端查看系統&#xff0c;實現實時目標追蹤和可視化…

零基礎學編程,中文編程工具之進度標尺構件的編程用法

零基礎學編程&#xff0c;中文編程工具之進度標尺構件的編程用法 一、前言 今天給大家分享的中文編程開發語言工具 進度條構件的用法。 編程入門視頻教程鏈接 https://edu.csdn.net/course/detail/39036 編程工具及實例源碼文件下載可以點擊最下方官網卡片——軟件下載——…

機器人持續學習基準LIBERO系列9——數據集軌跡查看

0.前置 機器人持續學習基準LIBERO系列1——基本介紹與安裝測試機器人持續學習基準LIBERO系列2——路徑與基準基本信息機器人持續學習基準LIBERO系列3——相機畫面可視化及單步移動更新機器人持續學習基準LIBERO系列4——robosuite最基本demo機器人持續學習基準LIBERO系列5——…

Python AI 實現繪畫功能(附帶源碼)

本文我們將為大家介紹如何基于一些開源的庫來搭建一套自己的 AI 作圖工具。 需要使用的開源庫為 Stable Diffusion web UI&#xff0c;它是基于 Gradio 庫的 Stable Diffusion 瀏覽器界面 Stable Diffusion web UI GitHub 地址&#xff1a;GitHub - AUTOMATIC1111/stable-dif…

快速解決maven依賴沖突

我們在開發過程中經常出現maven依賴沖突&#xff0c;或者maven版本不匹配的情況&#xff0c;我們可以使用阿里云原生腳手架來做maven管理&#xff0c;添加需要的組件&#xff0c;然后點擊獲取代碼&#xff0c;就可以獲得對應的依賴文件。

【重要公告】對BSV警報系統AS的釋義

??發表時間&#xff1a;2024年2月15日 由BSV區塊鏈協會開發并管理的BSV警報系統&#xff08;Alert System&#xff0c;以下簡稱“AS”&#xff09;是BSV網絡的重要組件。它是一個復雜的系統&#xff0c;主要職能是在BSV區塊鏈網絡內發布信息。這些信息通常與網絡訪問規則NAR相…

c# 任務(Task)介紹

任務&#xff08;Task&#xff09; Task作為C#異步的核心&#xff0c;Task位于System.Threading.Tasks命名空間下。 創建任務的基本原理是使用線程池&#xff0c;也就是說任務最終也是要交給線程去執行的。但是微軟優化了任務的線程池&#xff0c;使線程的控制更加精準和高效…

自定義TypeHandler

自定義TypeHandler 繼承BaseTypeHandler指定具體的泛型 MappedTypes({Date.class}) MappedJdbcTypes({JdbcType.DATE}) public class DateTimeWithTImeZoneTypeHandler extends BaseTypeHandler<Date> {Log log LogFactory.getLog(DateTimeWithTImeZoneTypeHandler.cl…

C++基于多設計模式下的同步異步日志系統day4

&#x1f4df;作者主頁&#xff1a;慢熱的陜西人 &#x1f334;專欄鏈接&#xff1a;C基于多設計模式下的同步&異步日志系統 &#x1f4e3;歡迎各位大佬&#x1f44d;點贊&#x1f525;關注&#x1f693;收藏&#xff0c;&#x1f349;留言 只要內容主要實現了同步日志消息…

Kubernetes的Sevice管理

服務原理: 所有服務都是根據這個服務衍生或者變化出來,根服務---- 服務感知后端靠標簽 slelector 標簽選擇器 kubectl label pods web1 appweb kubectl cluter-info dump | grep -i service-cluster-ip-range 服務ip取值范圍 Service 管理: 創建服務: --- kind: Serv…

React富文本編輯器開發(六)

現在&#xff0c;相關的基礎知識我們應該有個大概的了解了&#xff0c;但離我們真正的開發出一個實用型的組件還有一段距離&#xff0c;不過不用擔心&#xff0c;我們離目標已經越來越近。 以現在我們所了解的內容而言&#xff0c;或許你發現了一個問題&#xff0c;就是我們的編…