Java并發編程(四)線程同步 中 [AQS/Lock]

概述

Java中可以通過加鎖,來保證多個線程訪問某一個公共資源時,資源的訪問安全性。Java提出了兩種方式來加鎖

  • 第一種是我們上文提到的通過關鍵字synchronized加鎖,synchronized底層托管給JVM執行的,并且在java 1.6 以后做了很多優化(偏向鎖、自旋、輕量級鎖),使用很方便且性能也很好,所以在非必要的情況下,建議使用synchronized做同步操作;
  • 第二種是本文將要介紹的通過java.util.concurrent包下的Lock來加鎖(lock大量使用CAS+自旋。因此根據CAS特性建議在低鎖沖突的情況下使用lock)

AQS

概述

  • AQS全稱AbstractQueuedSynchronizer,譯為抽象隊列同步器
  • AQS底層數據結構是被volatile修飾state和一個Node雙向隊列
  • Lock下的實現類包括ReentrantLock、ReadLock、WriteLock底層都是基于AQS實現鎖資源獲取或釋放

內部結構

根據源碼我們可以知道AQS維護了一個volatile的state和一個CLH(FIFO)雙向隊列

state是一個由volatile修飾的int型互斥變量,state=0表示沒有任務線程使用該資源,而state>=1表示已經有線程正在持有鎖資源。CLH隊列是由內部類Node來維護的FIFO隊列

實現原理

當一個線程獲取鎖資源時首先會判斷state是否等于0(無鎖狀態),如果是0則把這個state更新為1,此時該鎖資源被占用。在這個過程中,如果多個線程同時進行state更新操作,就會導致線程的安全性問題。因此AQS底層采用了CAS機制,來保證互斥變量state更新的原子性。未獲得鎖的線程通過Unsafe類中的park方法去進行阻塞,把阻塞的線程按照先進先出的原則放到CLH雙向鏈表中,當獲得鎖的線程釋放鎖后,會從這個雙向鏈表的頭部去喚醒下一個等待的線程再去競爭鎖。

公平鎖和非公平鎖

在競爭鎖資源時,公平鎖要判斷雙向鏈表中是否有阻塞的線程,如果有則需要去排隊等待。而非公平鎖的處理方式是,不管雙向鏈表中是否有阻塞的線程在排隊等待,它都會去嘗試修改state變量去競爭鎖,這對鏈表中排隊的線程來說是非公平的。

Lock接口

Lock實現類

  • JDK8中,除了StampedLock為不可重入鎖,其他包括ReentrantLock、ReentrantReadWriteLock以及Synchronized關鍵字都是可重入鎖
  • 可重入鎖是指一個線程搶占到了互斥鎖資源且在鎖釋放之前可以重復獲取該鎖資源,只需要記錄重入次數state遞增1即可
  • lock實際上是通過更新AQS中的state來控制鎖的持有情況

Lock方法

// 嘗試獲取鎖,獲取成功則返回,否則阻塞當前線程
void lock();
// 嘗試獲取鎖,線程在成功獲取鎖之前被中斷,則放棄獲取鎖,拋出異常
void lockInterruptibly() throws InterruptedException;
// 嘗試獲取鎖,獲取鎖成功則返回true,否則返回false
boolean tryLock();
// 嘗試獲取鎖,若在規定時間內獲取到鎖,則返回true,否則返回false,未獲取鎖之前被中斷,則拋出異常
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 釋放鎖
void unlock();
// 返回當前鎖的條件變量,通過條件變量可以實現類似notify和wait的功能,一個鎖可以有多個條件變量
Condition newCondition();

ReentrantLock

  • 根據源碼可以看到實現鎖功能的關鍵成員變量Sync類型的sync繼承AQS
  • Sync在ReentrantLock中有兩個實現類NonfairSync公平鎖類型和FairSync非公平鎖類型
  • ReentrantLock默認是非公平鎖實現,在實例化時可以指定選擇公平鎖或者非公平鎖

ReentrantLock獲取鎖流程

//.lock()調用的是AQS的acquire()
public void lock() {sync.acquire(1);
}public final void acquire(int arg) {//tryAcquire:會嘗試通過CAS獲取一次鎖。//addWaiter:將當前線程加入雙向鏈表(等待隊列)中//acquireQueued:通過自旋,判斷當前隊列節點是否可以獲取鎖if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}//---------------------非公平鎖嘗試獲取鎖的過程---------------------
protected final boolean tryAcquire(int acquires) {// AQS的nonfairTryAcquire()方法return nonfairTryAcquire(acquires);
}final boolean nonfairTryAcquire(int acquires) {// 獲取當前線程final Thread current = Thread.currentThread();// 獲取stateint c = getState();if (c == 0) {// 目前沒有線程獲取鎖,通過CAS(樂觀鎖)去修改state的值if (compareAndSetState(0, acquires)) {// 設置持有鎖的線程為當前線程setExclusiveOwnerThread(current);return true;}}// 鎖的持有者是當前線程(重入鎖)else if (current == getExclusiveOwnerThread()) {// state + 1int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
}//---------------------當前線程加入雙向鏈表的過程---------------------
private Node addWaiter(Node mode) {Node node = new Node(mode);for (;;) {// 獲取末位節點Node oldTail = tail;if (oldTail != null) {// 當前節點的prev設置為原末位節點node.setPrevRelaxed(oldTail);// CAS確保在線程安全的情況下,將當前線程加入到鏈表的尾部if (compareAndSetTail(oldTail, node)) {// 原末位節點的next設置為當前節點oldTail.next = node;return node;}} else {// 鏈表為空則初始化initializeSyncQueue();}}
}//---------------------首節點自旋過程---------------------
final boolean acquireQueued(final Node node, int arg) {boolean interrupted = false;try {for (;;) {final Node p = node.predecessor();// 首節點線程去嘗試競爭鎖if (p == head && tryAcquire(arg)) {// 成功獲取到鎖,從首節點移出(FIFO)setHead(node);p.next = null; // help GCreturn interrupted;}if (shouldParkAfterFailedAcquire(p, node))interrupted |= parkAndCheckInterrupt();}} catch (Throwable t) {cancelAcquire(node);if (interrupted)selfInterrupt();throw t;}
}

ReentrantLock釋放鎖流程

釋放鎖本質就是對AQS中的狀態值State進行逐步遞減操作

//.unlock()調用AQS的release()方法釋放鎖資源
public void unlock() {sync.release(1);
}public final boolean release(int arg) {// Sync的tryRelease()方法if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;
}protected final boolean tryRelease(int releases) {// 獲取狀態int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;// 修改鎖的持有者為nullif (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;
}

ReentrantReadWriteLock

  • ReentrantReadWriteLock讀寫鎖可以分別獲取讀鎖或寫鎖,即將數據的讀寫操作分開;
  • writeLock():獲取寫鎖
    • writeLock().lock():為寫鎖上鎖
    • writeLock().unlock():為寫鎖釋放鎖
  • readLock():獲取讀鎖
    • readLock().lock():為讀鎖上鎖
    • readLock().unlock():為讀鎖釋放鎖
  • 讀鎖使用共享模式,寫鎖使用獨占模式。即不存在寫鎖時,讀鎖可以被多個線程同時持有;存在寫鎖時,除了獲得寫鎖的這個線程可以獲得讀鎖外,其他線程不能獲得讀鎖;而當有讀鎖時,寫鎖就不能獲得
  • 適用于讀多寫少應用場景,如緩存

Condition

概述?

  • Condition也是一種線程通信的機制,通過await和singalAll()實現線程阻塞和喚醒
  • 底層數據結構是復用AQS的Node類,由不帶頭結點的鏈表實現的隊列
  • await實現原理:通過LockSupport.park將當前線程置于Waiting阻塞狀態,直到其他線程調用signal或signalAll將等待隊列的隊頭結點移入到同步隊列中,使其有機會通過自旋獲取到鎖
  • signal/signalAll:將等待隊列的隊頭結點移入到同步隊列中,并通過LockSupport.unpark喚醒該線程
  • 與Object的wait/notify機制對比
    • Condition支持不響應中斷,而object不能
    • Lock可以支持多個condition等待隊列,object只能支持一個
    • Condition能夠對await設置超時時間,而object不能
  • 可以通過Lock+Condition實現生產者-消費者問題(在后文并發實踐篇會有相關示例)

Condition實踐

package com.bierce;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*** Lock + Condition: 實現線程按序打印* 案例:開啟3個線程,id分別為A、B、C,并打印10次,而且按順序交替打印如:ABCABCABC...*/
public class TestCondition {public static void main(String[] args) {PrintByOrderDemo print = new PrintByOrderDemo();new Thread(() -> {for (int i = 1; i <= 10; i++) {// 打印10次print.loopA(i);}},"A").start();new Thread(() -> {for (int i = 1; i <= 10; i++) {print.loopB(i);}},"B").start();new Thread(() -> {for (int i = 1; i <= 10; i++) {print.loopC(i);}},"C").start();}
}
class PrintByOrderDemo{private int number = 1; //當前正在執行線程的標記private Lock lock = new ReentrantLock();private Condition ConditionA = lock.newCondition();private Condition ConditionB = lock.newCondition();private Condition ConditionC = lock.newCondition();public void loopA(int totalLoop){lock.lock();try {if ( number != 1){ //判斷當前是否打印AConditionA.await();}for (int i = 1; i <= 1; i++) {System.out.print(Thread.currentThread().getName()); //打印A}//喚醒其他線程number = 2;ConditionB.signal();}catch (Exception e){e.printStackTrace();} finally {lock.unlock();}}public void loopB(int totalLoop){lock.lock();try {if ( number != 2){ //判斷當前是否打印BConditionB.await();}for (int i = 1; i <= 1; i++) {System.out.print(Thread.currentThread().getName()); //打印B}//喚醒其他線程number = 3;ConditionC.signal();}catch (Exception e){e.printStackTrace();} finally {lock.unlock();}}public void loopC(int totalLoop){lock.lock();try {if ( number != 3){ //判斷當前是否打印CConditionC.await();}for (int i = 1; i <= 1; i++) {System.out.print(Thread.currentThread().getName()); //打印C}//喚醒其他線程number = 1;ConditionA.signal();}catch (Exception e){e.printStackTrace();} finally {lock.unlock();}}
}

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

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

相關文章

一百五十二、Kettle——Kettle9.3.0本地連接Hive3.1.2(踩坑,親測有效,附截圖)

一、目的 由于先前使用的kettle8.2版本在Linux上安裝后&#xff0c;創建共享資源庫點擊connect時頁面為空&#xff0c;后來采用如下方法&#xff0c;在/opt/install/data-integration/ui/menubar.xul文件里添加如下代碼 <menuitem id"file-openZiyuanku" label&…

音視頻學習-音視頻基礎

文章目錄 一、 音視頻錄制原理二、音視頻播放原理三、圖像基礎概念1.像素2.分辨率3.位深4.幀率5.碼率6.Stride跨距 四、RGB、YUV1.RGB2.YUV1. 4:4:4格式2. 4:2:2格式3. 4:2:0格式4. 4:2:0數據格式對比 3.RGB和YUV的轉換4.YUV Stride對齊問題 五、視頻的主要概念1.基本概念2.I P…

數據結構:棧和隊列(超詳細)

目錄 ?編輯 棧&#xff1a; 棧的概念及結構&#xff1a; 棧的實現&#xff1a; 隊列&#xff1a; 隊列的概念及結構&#xff1a; 隊列的實現&#xff1a; 擴展知識&#xff1a; 以上就是個人學習線性表的個人見解和學習的解析&#xff0c;歡迎各位大佬在評論區探討&#…

PHP substr()函數詳解,PHP截取字符串。

「作者主頁」&#xff1a;士別三日wyx 「作者簡介」&#xff1a;CSDN top100、阿里云博客專家、華為云享專家、網絡安全領域優質創作者 「推薦專欄」&#xff1a;對網絡安全感興趣的小伙伴可以關注專欄《網絡安全入門到精通》 substr 一、截取字符串二、截取中文字符串三、leng…

clickhouse集群部署

一、集群部署簡介 部署的詳情可以看官網 先部署兩個server,三個keeper[zookeeper] clickhouse之前依賴的存儲是zookeeper,后來改為了keeper,官網給出了原因 所以這就決定了clickhouse有兩種安裝方式&#xff0c;依賴于keeper做存儲或者依賴于zookeeper做存儲 二、zookeeper作…

注冊中心 —— SpringCloud Netflix Eureka

Eureka 簡介 Eureka 是一個基于 REST 的服務發現組件&#xff0c;SpringCloud 將它集成在其子項目 spring-cloud-netflix 中&#xff0c;以實現 SpringCloud 的服務注冊與發現&#xff0c;同時提供了負載均衡、故障轉移等能力&#xff0c;目前 Eureka2.0 已經不再維護&#xf…

基于YOLOv8模型和Caltech數據集的行人檢測系統(PyTorch+Pyside6+YOLOv8模型)

摘要 基于YOLOv8模型和Caltech數據集的行人檢測系統可用于日常生活中檢測與定位行人&#xff0c;利用深度學習算法可實現圖片、視頻、攝像頭等方式的行人目標檢測&#xff0c;另外本系統還支持圖片、視頻等格式的結果可視化與結果導出。本系統采用YOLOv8目標檢測算法訓練數據集…

C#使用FileInfo和DirectoryInfo類來執行文件和文件夾操作

System.IO.FileInfo 和 System.IO.DirectoryInfo 是C#中用于操作文件和文件夾的類&#xff0c;它們提供了許多有用的方法和屬性來管理文件和文件夾。 System.IO.FileInfo&#xff1a; FileInfo 類用于操作單個文件的信息和內容。以下是一些常用的方法和屬性&#xff1a; Exi…

頻繁full gc 調參

Error message from spark is:java.lang.Exception: application_1678793738534_17900289 Driver Disassociated [akka.tcp://sparkDriverClient11.71.243.117:37931] <- [akka.tcp://sparkYarnSQLAM9.10.130.149:38513] disassociated! 日志里頻繁full gc &#xff0c;可以…

Python Opencv實踐 - 圖像金字塔

import cv2 as cv import numpy as np import matplotlib.pyplot as pltimg cv.imread("../SampleImages/pomeranian.png", cv.IMREAD_COLOR) print(img.shape)#圖像上采樣 #cv.pyrUp(src, dstNone, dstsizeNone, borderTypeNone) #參考資料&#xff1a;https://blo…

js實現將文本轉PDF格式并下載到本地

html里面需要引入jspdf.umd.min.js和FileSaver.js jspdf.umd.min.js&#xff1a;https://www.npmjs.com/package/jspdf FileSaver.js&#xff1a;https://download.csdn.net/download/weixin_45791806/87272893?spm1001.2014.3001.5503 同時項目的根部目錄也需要引入SimHei.tt…

單片機之從C語言基礎到專家編程 - 4 C語言基礎 - 4.7 進制及其轉換

進制是數字的進位計數制&#xff0c;R進制也就是逢R進一。計算機只能識別二進制&#xff0c;也就是逢二進一&#xff0c;例如&#xff0c;11在十進制中為2&#xff0c;在二進制中逢2進1&#xff0c;則為10。以下為進制表示表。 二進制三進制八進制九進制十進制十六進制0000001…

【LeetCode 算法】Find the Losers of the Circular Game 找出轉圈游戲輸家

文章目錄 Find the Losers of the Circular Game 找出轉圈游戲輸家問題描述&#xff1a;分析代碼模擬 Tag Find the Losers of the Circular Game 找出轉圈游戲輸家 問題描述&#xff1a; n 個朋友在玩游戲。這些朋友坐成一個圈&#xff0c;按 順時針方向 從 1 到 n 編號。從…

AD域控制器將輔域控制器角色提升為主域控制器

背景 域控服務器遷移&#xff0c;已將新機器添加為該域的輔域控制器。 主域控制器&#xff1a;test-dc-01 輔域控制器&#xff1a;test-dc-02 需求將主輔域的角色進行互換&#xff0c;test-dc-01更換為輔域&#xff0c;test-dc-02更換為主域。 操作步驟 方法1 命令行修改AD域…

Datawhale Django入門組隊學習Task02

Task02 首先啟動虛擬環境&#xff08;復習一下之前的&#xff09; 先退出conda的&#xff0c; conda deactivate然后cd到我的venv下面 &#xff0c;然后cd 到 scripts&#xff0c;再 activate &#xff08;powershell里面&#xff09; 創建admin管理員 首先cd到項目路徑下&a…

mySQL 視圖 VIEW

簡化版的創建視圖 create view 視圖名 as select col ...coln from 表create view 視圖名&#xff08;依次別名&#xff09; as select col ...coln from 表create view 視圖名 as select col “別名1”&#xff0c;。。。col "別名n" from 表show tab…

Flink的常用算子以及實例

1.map 特性&#xff1a;接收一個數據&#xff0c;經過處理之后&#xff0c;就返回一個數據 1.1. 源碼分析 我們來看看map的源碼 map需要接收一個MapFunction<T,R>的對象&#xff0c;其中泛型T表示傳入的數據類型&#xff0c;R表示經過處理之后輸出的數據類型我們繼續往…

計算機提示vcruntime140_1.dll丟失的解決方法

在使用Windows操作系統時&#xff0c;有時候我們可能會遇到一些應用程序無法正常運行的問題&#xff0c;出現錯誤提示&#xff0c;其中之一可能就是缺少或損壞了vcruntime140_1.dll文件。在遇到這種情況時&#xff0c;我們可以嘗試修復vcruntime140_1.dll文件來解決問題。 先科…

后端 springboot 給 vue 提供參數

前端 /** 發起新增或修改的請求 */requestAddOrEdit(formData) {debuggerif(formData.id undefined) {formData.id }getAction(/material/getNameModelStandard, {standard: this.model.standard,name: this.model.name,model: this.model.model}).then((res) > {if (res …

《零基礎7天入門Arduino物聯網-06》程序基礎-編程語言是什么

配套視頻課程&#xff1a;《零基礎學Arduino物聯網&#xff0c;入門到進階》 配套課件資料獲取&#xff1a;微聯實驗室 配套學習套件購買&#xff1a;淘寶搜索店鋪【微聯實驗室】 程序基礎-編程語言是什么 程序是什么 程序設計可以理解為是用計算機語言創造出一系列指令的過程…