簡單了解下Java中鎖的概念和原理

你好,這里是codetrend專欄“高并發編程基礎”。

Java提供了很多種鎖的接口和實現,通過對各種鎖的使用發現理解鎖的概念是很重要的。

Java的鎖通過java代碼實現,go語言的鎖通過go實現,python語言的鎖通過python實現。它們都實現的什么呢?這部分就是鎖的定義和設計模式、算法、原理等一些理論上的東西。

下文基于此說明Java常見的鎖分類和原理。

樂觀鎖&悲觀鎖

樂觀鎖和悲觀鎖是在并發編程中保證數據一致性的兩種常見的鎖機制。

樂觀鎖:樂觀鎖假設在大多數情況下,不會出現并發沖突,因此在讀取數據時并不加鎖,只有在提交更新時才會檢查是否有其他并發操作修改了數據。如果檢測到了沖突,就放棄當前的操作并返回錯誤信息。通常采用版本號或時間戳等機制來實現樂觀鎖。樂觀鎖機制適用于讀操作頻繁、寫操作較少的場景。

悲觀鎖:悲觀鎖則假設并發沖突隨時都可能發生,因此在讀取數據時就會加鎖,直到操作完成后才會釋放鎖。一般可以使用數據庫中的行級鎖、表級鎖或者使用 synchronized 等語言提供的鎖機制來實現悲觀鎖。悲觀鎖機制適用于寫操作頻繁、讀操作較少的場景。

選擇何種鎖機制應根據具體的應用場景進行選擇。在讀寫比例不明顯的情況下,可以考慮使用樂觀鎖機制,這樣可以減少鎖競爭帶來的性能損失。如果讀寫比例明顯,考慮使用悲觀鎖機制可以更好地確保數據的一致性。

需要注意的是,在實際應用中,樂觀鎖和悲觀鎖并不是嚴格的對立關系,而是可以結合使用的。例如,在高并發場景中,可以使用樂觀鎖機制來減少對數據庫的壓力,但在必要的時候也可以使用悲觀鎖機制來確保數據的一致性。

下面是使用 Java 實現一個簡單的樂觀鎖和悲觀鎖的示例:

樂觀鎖示例

import java.util.concurrent.atomic.AtomicInteger;public class OptimisticLock {private AtomicInteger value = new AtomicInteger(0);public void increment() {int oldValue, newValue;while (!value.compareAndSet(oldValue, newValue)){// CAS操作,如果值沒有被修改,則更新為新值// 讀取當前值oldValue = value.get();// 計算新值newValue = oldValue + 1;} }public int getValue() {return value.get();}
}

悲觀鎖示例

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class PessimisticLock {private int value = 0;private Lock lock = new ReentrantLock();public void increment() {lock.lock(); // 加鎖try {value++; // 更新數據} finally {lock.unlock(); // 解鎖}}public int getValue() {return value;}
}

公平鎖&非公平鎖

公平鎖:公平鎖保證線程獲取鎖的順序與其請求鎖的順序相同,即先到先得。當多個線程同時競爭同一個公平鎖時,這些線程會按照先后順序排隊等待獲取鎖。公平鎖可以避免饑餓現象,但是由于需要維護一個有序隊列,因此性能較低。

非公平鎖:非公平鎖則不保證線程獲取鎖的順序,即先到不一定先得。當一個線程釋放鎖時,如果有多個線程正在等待獲取鎖,那么當前持有鎖的線程有可能會再次獲取到鎖,而不是讓等待時間最長的線程獲取鎖。非公平鎖具有更高的吞吐量和更低的競爭開銷,但是容易導致某些線程長時間等待,出現饑餓現象。

在 Java 中,可以使用 ReentrantLock 類來實現公平鎖和非公平鎖。默認情況下,ReentrantLock 類使用非公平鎖,可以通過構造函數傳入 true 來創建公平鎖,例如:

// 創建公平鎖
Lock fairLock = new ReentrantLock(true);// 創建非公平鎖
Lock unfairLock = new ReentrantLock(false);

自旋鎖&適應性自旋鎖

自旋鎖和適應性自旋鎖都是基于忙等待的鎖機制,它們在獲取鎖時不會立即阻塞線程,而是反復檢查鎖的狀態,直到獲取到鎖為止。下面對每種鎖做一些說明,并提供Java中的實現示例。

自旋鎖:自旋鎖適合用于鎖持有時間非常短暫的情況,可以避免線程切換帶來的開銷。自旋鎖的基本思想是,當線程發現共享資源已經被其他線程占用時,就進行自旋等待,直到占用共享資源的線程釋放鎖為止。Java中的ReentrantLock就支持自旋鎖,可以通過構造函數的參數來設置自旋次數,例如:

ReentrantLock lock = new ReentrantLock(true); // 使用公平鎖
lock.lock(); // 獲取鎖
// 共享資源的訪問操作
lock.unlock(); // 釋放鎖

適應性自旋鎖:適應性自旋鎖是一種優化過的自旋鎖,它會根據前一次在同一個鎖上的自旋時間和鎖的擁有者情況來確定自旋次數。如果在同一個鎖上,前一次自旋的時間較長,那么下一次就會更傾向于阻塞線程而不是自旋等待。這樣可以避免長時間的自旋等待,減少資源的浪費。Java中的StampedLock就是支持適應性自旋鎖的一種鎖機制,例如:

StampedLock lock = new StampedLock();
long stamp = lock.readLock(); // 獲取悲觀讀鎖
// 共享資源的訪問操作
lock.unlockRead(stamp); // 釋放悲觀讀鎖

無鎖 & 偏向鎖 & 輕量級鎖 & 重量級鎖

無鎖、偏向鎖、輕量級鎖和重量級鎖都是Java中不同的鎖狀態,用于實現線程同步的機制。

無鎖:無鎖是指在多線程環境下,對共享資源的訪問沒有任何同步控制,所有線程可以同時訪問共享資源,不會發生爭用。無鎖適用于只讀操作或者線程沖突非常少的情況。例如:

int value = sharedValue; // 讀取共享資源

偏向鎖:偏向鎖是一種針對加鎖操作進行優化的機制,它適用于只有一個線程反復獲取鎖的情況。當一個線程首次獲取鎖時,會將鎖的標記設置為該線程,下次該線程再次獲取鎖時無需競爭,直接進入臨界區。偏向鎖的目標是提高單線程下的性能。例如:

// 線程1首次獲取鎖
synchronized (lock) {// 臨界區代碼
}// 線程1再次獲取鎖
synchronized (lock) {// 臨界區代碼
}

輕量級鎖:輕量級鎖是一種基于CAS(Compare and Swap)操作的鎖機制,它適用于多個線程交替執行同一段臨界區代碼的情況。當一個線程獲取鎖時,會嘗試使用CAS操作將對象頭部的鎖記錄替換為指向自己的線程ID,如果成功,則表示獲取鎖成功;否則,表示有其他線程競爭鎖,可能發生鎖膨脹。例如:

Lock lock = new ReentrantLock();
lock.lock(); // 獲取鎖
try {// 臨界區代碼
} finally {lock.unlock(); // 釋放鎖
}

重量級鎖:重量級鎖是一種基于操作系統互斥量(Mutex)的鎖機制,它適用于多個線程頻繁競爭同一把鎖的情況。當一個線程獲取鎖時,會進入阻塞狀態,直到鎖被釋放,然后喚醒其他線程進行競爭。重量級鎖適用于線程沖突比較頻繁的情況。例如:

synchronized (obj) {// 臨界區代碼
}

可重入鎖 & 非可重入鎖

可重入鎖:可重入鎖是指允許同一線程多次獲取同一把鎖,而不會發生死鎖或者其他異常情況。當一個線程獲取鎖后,再次嘗試獲取鎖時會自動成功,并且需要釋放相同次數的鎖才能真正釋放鎖。Java中的ReentrantLock和synchronized都支持可重入鎖,例如:

// ReentrantLock示例
ReentrantLock lock = new ReentrantLock();
lock.lock(); // 獲取鎖
try {// 臨界區代碼lock.lock(); // 再次獲取鎖try {// 嵌套臨界區代碼} finally {lock.unlock(); // 釋放嵌套鎖}
} finally {lock.unlock(); // 釋放鎖
}

非可重入鎖:非可重入鎖是指同一線程不能多次獲取同一把鎖,否則會導致死鎖或者其他異常情況。Java中的普通對象鎖就是一種非可重入鎖,例如:

package engineer.concurrent.battle.glock;public class LockEnterNoRepeat {private static Object lock = new Object();public static void main(String[] args) {synchronized (lock) {// 臨界區代碼synchronized (lock) { // 再次獲取鎖會導致死鎖// 嵌套臨界區代碼System.out.println("thread end");}}}
}

需要注意的是,可重入鎖雖然提供了更大的靈活性和便利性,但也要注意避免死鎖和其他問題的發生。在設計和使用多線程代碼時,應該根據具體情況選擇合適的鎖機制。

獨享鎖 & 共享鎖

獨享鎖Exclusive Lock是指在某個時間點只允許一個線程持有鎖,其他線程不能同時持有該鎖。獨享鎖也被稱為排它鎖或寫鎖,用于保護臨界資源的獨占訪問。

共享鎖Shared Lock是指在某個時間點允許多個線程同時持有鎖,這些線程可以同時訪問被保護的資源,但是不能進行寫操作。共享鎖也被稱為讀鎖,用于實現讀多寫少的并發模式。

在Java中,ReentrantReadWriteLock是一種同時支持獨享鎖和共享鎖的鎖機制。通過使用ReentrantReadWriteLock,可以在不同的線程之間實現對共享資源的讀寫操作控制。

下面是使用ReentrantReadWriteLock實現獨享鎖和共享鎖的示例代碼:

import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadWriteLockExample {private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();private String data = "Hello, World!";public void readData() {lock.readLock().lock();try {System.out.println("Reading data: " + data);} finally {lock.readLock().unlock();}}public void writeData(String newData) {lock.writeLock().lock();try {System.out.println("Writing data: " + newData);data = newData;} finally {lock.writeLock().unlock();}}public static void main(String[] args) {ReadWriteLockExample example = new ReadWriteLockExample();// 創建多個讀線程for (int i = 0; i < 3; i++) {Thread readerThread = new Thread(() -> {example.readData();});readerThread.start();}// 創建一個寫線程Thread writerThread = new Thread(() -> {example.writeData("New data");});writerThread.start();}
}

運行該程序會輸出以下結果:

Reading data: Hello, World!
Reading data: Hello, World!
Reading data: Hello, World!
Writing data: New data

可以看到,有三個讀線程同時獲取了共享鎖,并讀取了數據。而在寫線程中,只有該線程獲取了獨享鎖,并成功修改了數據。

通過ReentrantReadWriteLock,我們可以實現對共享資源的讀操作并發執行,提高讀操作的效率;而寫操作會獨占鎖,保證在寫操作時只有一個線程能夠訪問臨界資源,確保數據一致性。

關于作者

來自全棧程序員nine的探索與實踐,持續迭代中。

歡迎關注和點贊~

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

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

相關文章

Apache Calcite Linq4j學習

Lin4j簡介 Linq4j是Apache Calcite項目中的一個模塊&#xff0c;它提供了類似于LINQ&#xff08;Language-Integrated Query&#xff09;的功能&#xff0c;用于在Java中進行數據查詢和操作。Linq4j可以將邏輯查詢轉換為物理查詢&#xff0c;支持對集合進行篩選、映射、分組等…

python自動例化verilog

python自動例化verilog 使用方法&#xff1a;在gvim頁面&#xff0c;使用命令自動例化 :r !AUTO_inst xxx.v #python import re import sysmdl_re r"\s*module\s*(?P<mname>\w) *" port_re r"\s*(?P<dir>input|output)\s(?P<typ>wire|re…

API-節點操作

學習目標&#xff1a; 掌握節點操作 學習內容&#xff1a; DOM節點查找節點增加節點刪除節點 DOM節點&#xff1a; DOM樹里每一個內容都稱之為節點。 節點類型 元素節點所有的標簽比如body、div&#xff1b;html是根節點屬性節點所有的屬性&#xff0c;比如href文本節點所有…

FastAPI-Cookie

fastapi-learning-notes/codes/ch01/main.py at master Relph1119/fastapi-learning-notes GitHub 1、Cookie的作用 Cookie可以充當用戶認證的令牌&#xff0c;使得用戶在首次登錄后無需每次手動輸入用戶名和密碼&#xff0c;即可訪問受限資源&#xff0c;直到Cookie過期或…

《PyTorch計算機視覺實戰》:一、二章

目錄 第一章&#xff1a;人工神經網絡基礎 比較人工智能和傳統機器學習 人工神經網絡&#xff08;Artificial Neural Network&#xff0c;ANN&#xff09; 是一種受人類大腦運作方式啟發而構建的監督學習算法。神經網絡與人類大腦中神經元連接和激活的方式比較類似&#xff0…

mysql查看用戶的過期時間

1. mysql查看用戶的過期時間的方法 在MySQL中&#xff0c;用戶的過期時間&#xff08;也稱為賬戶過期日期&#xff09;是一個可選項&#xff0c;用于確定某個MySQL用戶賬戶何時到期。但是&#xff0c;值得注意的是&#xff0c;并非所有的MySQL安裝或版本都支持直接設置用戶賬戶…

GoLang語言

基礎 安裝Go擴展 go build 在項目目錄下執行go build go run 像執行腳本文件一樣執行Go代碼 go install go install分為兩步&#xff1a; 1、 先編譯得到一個可執行文件 2、將可執行文件拷貝到GOPATH/bin Go 命令 go build :編譯Go程序 go build -o "xx.exe"…

CSS元素之間的空白問題:原因與解決方案

在網頁設計中&#xff0c;CSS元素之間的空白是一個常見但往往不易察覺的問題。空白不僅影響布局的準確性&#xff0c;還可能破壞設計的整體美感。本文將探討元素之間空白的產生原因&#xff0c;并提供有效的解決方案。 空白產生的根源 空白問題主要發生在行內元素和行內塊元素…

4.x86游戲實戰-人物狀態標志位

免責聲明&#xff1a;內容僅供學習參考&#xff0c;請合法利用知識&#xff0c;禁止進行違法犯罪活動&#xff01; 本次游戲沒法給 內容參考于&#xff1a;微塵網絡安全 上一個內容&#xff1a;3.x86游戲實戰-寄存器 人物狀態標志位&#xff1a; 什么叫人物狀態標志位&…

力扣刷題--3168. 候診室中的最少椅子數【簡單】

題目描述 給你一個字符串 s&#xff0c;模擬每秒鐘的事件 i&#xff1a; 如果 s[i] ‘E’&#xff0c;表示有一位顧客進入候診室并占用一把椅子。 如果 s[i] ‘L’&#xff0c;表示有一位顧客離開候診室&#xff0c;從而釋放一把椅子。 返回保證每位進入候診室的顧客都能有…

Leetcode[反轉鏈表]

LCR 024. 反轉鏈表 給定單鏈表的頭節點 head &#xff0c;請反轉鏈表&#xff0c;并返回反轉后的鏈表的頭節點。 示例 1&#xff1a; 輸入&#xff1a;head [1,2,3,4,5] 輸出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 輸入&#xff1a;head [1,2] 輸出&#xff1a;[2,1]示…

Go使用Gin框架開發的Web程序部署在Linux時,無法綁定監聽Ipv4端口

最近有寫一部分go語言開發的程序&#xff0c;在部署程序時發現&#xff0c;程序在啟動后并沒有綁定ipv4的端口&#xff0c;而是直接監聽綁定ipv6的端口。 當我用netstat -antup | grep 3601查找我的gin服務啟動的端口占用情況的時候發現&#xff0c;我的服務直接綁定了tcp6 &a…

240629_昇思學習打卡-Day11-Vision Transformer中的self-Attention

240629_昇思學習打卡-Day11-Transformer中的self-Attention 根據昇思課程順序來看呢&#xff0c;今兒應該看Vision Transformer圖像分類這里了&#xff0c;但是大概看了一下官方api&#xff0c;發現我還是太笨了&#xff0c;看不太明白。正巧昨天學SSD的時候不是參考了太陽花的…

LeetCode.30 串聯所有單詞的子串

問題描述 給定一個字符串 s 和一個字符串數組 words。 words 中所有字符串 長度相同。 s 中的 串聯子串 是指一個包含 words 中所有字符串以任意順序排列連接起來的子串。 例如&#xff0c;如果 words ["ab","cd","ef"]&#xff0c; 那么 &q…

MySQL Workbench支持哪些數據庫版本的連接?

MySQL Workbench支持哪些數據庫版本的連接&#xff1f; MySQL Workbench 是一款強大的數據庫管理和設計工具&#xff0c;它支持連接多種版本的 MySQL 數據庫。包括但不限于&#xff1a; MySQL 官方發行的所有版本&#xff0c;從 MySQL 5.0 到最新的 MySQL 8.x 和更高版本。 M…

Linux - 札記 - W10: Warning: Changing a readonly file

Linux - 札記 - W10: Warning: Changing a readonly file 這里寫目錄標題 一、問題描述1. 現象2. 原因 二、解決方案 一、問題描述 1. 現象 在使用 vim 編輯文件時&#xff08;我這里是要編輯 /root/.ssh/authorized_keys&#xff09;提示&#xff1a;W10: Warning: Changing…

【論文+代碼|已完結】基于人工智能的圖像識別技術在醫療診斷中的應用

基于人工智能的圖像識別技術在醫療診斷中的應用 摘要&#xff1a;隨著人工智能技術的飛速發展&#xff0c;圖像識別技術在醫療領域的應用日益廣泛。本畢業設計旨在研究基于人工智能的圖像識別技術在醫療診斷中的應用&#xff0c;通過對大量醫療圖像數據的分析和處理&#xff0…

IOS Swift 從入門到精通:ios 連接數據庫 安裝 Firebase 和 Firestore

創建 Firebase 項目 導航到Firebase 控制臺并創建一個新項目。為項目指定任意名稱。 在這里插入圖片描述 下一步,啟用 Google Analytics,因為我們稍后會用到它來發送推送通知。 在這里插入圖片描述 在下一個屏幕上,選擇您的 Google Analytics 帳戶(如果已創建)。如果沒…

<電力行業> - 《第7課:發電》

1 發電的原理 電力生產的發電環節是利用電能生產設備將各種一次能源或其他形式的能轉換為電能。生產電能的主要方式有火力發電、水力發電、核能發電、地熱發電、風力發電、太陽能發電、潮汐能發電、生物智能發電和燃料電池發電等。 除太陽能發電的光伏電池技術和燃料電池發電…

c++ 子類繼承父類

這個是子類繼承父類 是否重寫從父類那里繼承來的函數 這個例子的路徑 E盤 demo文件夾 fatherChildfunc