日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java 多线程 —— AQS 详解

發(fā)布時(shí)間:2025/3/12 java 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 多线程 —— AQS 详解 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

引言

AQS 是AbstractQuenedSynchronizer 的縮寫,抽象的隊(duì)列式同步器,它是除了java自帶的synchronized關(guān)鍵字之外的鎖機(jī)制。是 JUC 下的重要組件。

相關(guān)產(chǎn)物有:ReentrantLock、CountDownLatch、Semaphore、ReadWriteLock等。

一、AQS的設(shè)計(jì)思想

AbstractQuenedSynchronizer 維護(hù)了一個(gè) volatile int state 變量,代表共享資源。

若state 是0,代表資源空閑,當(dāng)前線程將 0 改為 1,表示上鎖,當(dāng)前線程置為工作線程;

若state不為0,代表資源占用,當(dāng)前線程依然會(huì) acquire() 一個(gè)資源,如果恰好是當(dāng)前的工作線程,那么state 累加,以此描述“重入性”;如果當(dāng)前線程并不是工作線程,則會(huì)被安置在一個(gè)由AQS維護(hù)的資源等待隊(duì)列。

AQS隊(duì)列會(huì)讓第一個(gè)線程N(yùn)ode自旋獲取資源,而后面的線程,則通過?LockSupport.park(this) 方法將線程置為 WAITING 狀態(tài)等待被喚醒。

如果第一個(gè)線程獲取到了資源,那么就將它設(shè)置為隊(duì)列的 head 節(jié)點(diǎn),原 head 就會(huì)被移出隊(duì)列。

AQS的設(shè)計(jì)中用到了模板方法模式,不同的資源共享機(jī)制如互斥或共享可以由子類自定義實(shí)現(xiàn):

Exclusive:如ReentrantLock、
Share:如信號量、閉鎖、讀寫鎖等。

AQS 的另一個(gè)特點(diǎn)是自旋+CAS。

在請求資源和入列等操作中,經(jīng)常會(huì)看到 for(;;) 、compareAndSetState、compareAndSetTail等操作,這與synchronized的實(shí)現(xiàn)有很大區(qū)別。

通過比較并設(shè)置的方式,可以有效提高資源獲取的效率,但同時(shí)也會(huì)消耗額外的CPU資源。

二、兩種資源訪問策略的代表

在AQS中維護(hù)了一個(gè) Node 節(jié)點(diǎn),它有兩種等待模式,同時(shí)也表示資源的兩種不同的訪問策略:

/** Marker to indicate a node is waiting in shared mode */ static final Node SHARED = new Node(); /** Marker to indicate a node is waiting in exclusive mode */ static final Node EXCLUSIVE = null;

由此,衍生出兩類不同的子類實(shí)現(xiàn),第一類是以ReentrantLock為代表的互斥鎖,它在語義上與synchronized實(shí)現(xiàn)了相同的互斥性和可重入性;另一類是以閉鎖CountDownLatch 為代表的線程同步工具。

2.1 ReentrantLock

首先 AQS 中的 state 為 0.

A 線程在執(zhí)行 lock() 方法后,以獨(dú)占方式 CAS state 為 1。AQS 會(huì)記錄 A 線程為當(dāng)前的獨(dú)占線程,其他線程如果再嘗試獲取資源,就會(huì)進(jìn)入等待隊(duì)列,直到 A 線程調(diào)用 unlock() 方法,釋放了資源,即 state 回歸 0 狀態(tài)。

在“重入性”方面,如果A線程第二次嘗試取鎖,state 會(huì)累加。也就是說,上鎖的次數(shù)一定等于釋放鎖的次數(shù)。

2.2 CountDownLatch

CountDownLatch 翻譯為 “閉鎖”或“門閂”,這是一種非常好用的同步工具,可以延遲線程的進(jìn)度直到終止?fàn)顟B(tài)。

與 ReentrantLock 不同的是,在構(gòu)造 CountDownLatch 對象的時(shí)候,會(huì)先設(shè)定一個(gè) state 大小:

CountDownLatch latch = new CountDownLatch(3);

這個(gè) 3 就是 state 變量的初始值,然后線程使用 countDown() 方法遞減這個(gè)計(jì)數(shù),直到 state = 0,放行所有 waiting 中的線程。

CountDownLatch 維護(hù)的 state 表示的是事件數(shù)量,當(dāng)指定數(shù)量的事件執(zhí)行完畢后,就會(huì) unpark() 主調(diào)線程,繼續(xù)后續(xù)動(dòng)作。

在使用CountDownLatch時(shí)有一個(gè)誤區(qū)是,state 的值就代表了線程的數(shù)量,認(rèn)為我 state = 3 ,就需要 3 個(gè)線程去執(zhí)行任務(wù),其實(shí),state = 10 也依然可以使用 一個(gè)線程去執(zhí)行,關(guān)鍵要區(qū)分事件與并行任務(wù)的概念。

三、ReentrantLock 源碼

作為補(bǔ)充 synchronized 的鎖機(jī)制,ReentrantLock 顯示鎖的功能非常強(qiáng)大,但這里不打算全面分析ReentrantLock的奇技淫巧,而是從 lock() 方法出發(fā),分析一下 AQS 是如何實(shí)現(xiàn)資源的鎖定和等待隊(duì)列的維護(hù)的。

3.1?acquire

acquire 是 AQS 的頂層入口,他表示獲取鎖資源。

public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt(); }

Doug Lea 的代碼非常簡潔,根本沒有一句廢話,就連代碼結(jié)構(gòu)也非常精簡,如果是分析源碼的話,我們可以嘗試去改寫一下這個(gè)方法,讓其可讀性更強(qiáng)一些:

public final void acquire(int arg) {if (!tryAcquire(arg)) {Node newWaiter = addWaiter(Node.EXCLUSIVE);boolean needInterrupt = acquireQueued(newWaiter, arg);if (needInterrupt) {selfInterrupt();}} }

從方法中的一系列方法名和判斷邏輯來看。

嘗試獲取資源,如果成功,則直接返回。如果不成功,addWaiter 添加一個(gè)獨(dú)占模式的等待者,acquireQueued 以排隊(duì)的方式去獲取資源。

3.2 tryAcquire

tryAcquire 在 ReentrantLock 中有兩種實(shí)現(xiàn),分別是:

FairSync 中的公平鎖實(shí)現(xiàn);

NonfairSync 中的非公平鎖實(shí)現(xiàn)

當(dāng)然,公平與非公平并不是重點(diǎn),就以非公平的實(shí)現(xiàn)來看一下,

final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false; }

當(dāng)前線程會(huì)?CAS state 0->1,或累加重入,成功返回true,失敗返回false。

3.3 addWaiter

在acquire上鎖操作失敗后,會(huì)執(zhí)行這個(gè)方法:

private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node; }

addWaiter ,添加一個(gè)等待者,它只完成一項(xiàng)工作,就是向等待隊(duì)列中添加一個(gè) Node:

1、將當(dāng)前線程封裝為一個(gè)隊(duì)列 Node;

2、取得隊(duì)列的尾節(jié)點(diǎn) tail,并CAS 新的節(jié)點(diǎn)設(shè)置為新的 tail

3、設(shè)置新 tail 成功,直接返回

4、若設(shè)置新 tail 不成功,或者干脆,原tail 就不存在,執(zhí)行 enq 方法,自旋操作以上步驟,直到成功。

enq方法是 enqueue 的縮寫,意思是“使隊(duì)列化”,它就是一個(gè) while-true ,如果隊(duì)列不存在,就創(chuàng)建一個(gè)隊(duì)列,如果隊(duì)列已經(jīng)存在,就把 node 放到最后一個(gè):

private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}} }

這里就用到了自旋操作,每次自旋都會(huì)獲取當(dāng)前的 tail 節(jié)點(diǎn),避免在設(shè)置的過程中間被其他線程加塞,卻又不知道。

剛進(jìn)入方法的時(shí)候,肯定需要走初始化的邏輯,這會(huì)創(chuàng)建一個(gè) 空的 Node 節(jié)點(diǎn)作為 head,所以由此我們也知道,AQS 隊(duì)列中的頭結(jié)點(diǎn)實(shí)際上就是一個(gè)沒有實(shí)際意義的功能型節(jié)點(diǎn),里邊是沒有線程的,真正封裝了線程的節(jié)點(diǎn)是從第二個(gè)節(jié)點(diǎn)開始。

總體來看,addWaiter 完全就是一個(gè) do-while 循環(huán),先執(zhí)行一次 CASTail,失敗后循環(huán)執(zhí)行CASTail,直到成功后返回該 node,同時(shí)也是新的 tail 節(jié)點(diǎn)。

3.4?acquireQueued

在 addWaiter 添加了新的 tail 后,需要做哪些事情呢?acquireQueued!

final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);} }

這里需要說明一下,該方法的邏輯兼顧了中斷的操作,如果對中斷機(jī)制不太了解,可以暫時(shí)不去理會(huì)。

該方法同樣是一個(gè) while-true 循環(huán),當(dāng)且僅當(dāng),當(dāng)前節(jié)點(diǎn)是隊(duì)列中第二個(gè)節(jié)點(diǎn)(addWaiter中已經(jīng)很明確,AQS隊(duì)列中的head 節(jié)點(diǎn)就是一個(gè)空的 Node),并且 tryAcquire 成功,才會(huì)返回。在返回之前,僅做了一些隊(duì)列的維護(hù)工作:設(shè)置新的head 節(jié)點(diǎn)。

如果沒有“當(dāng)且僅當(dāng)”,那么執(zhí)行 park:

private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted(); }

也就是說,除了第二個(gè)節(jié)點(diǎn)以后的節(jié)點(diǎn),都要進(jìn)行 park,即線程切換為 WAITING 狀態(tài)。

四、AQS acquire 流程

經(jīng)過了上一節(jié)的源碼分析,我們已經(jīng)大概清楚了 lock() 方法調(diào)用之后發(fā)生的事情,接下來就需要總結(jié)一下 acquire 流程步驟,提煉一下 AQS 隊(duì)列的工作原理:

總結(jié)

AQS 使用了大量的 CAS 操作,避免上鎖,你在ReentrantLock中看不到一句 synchronized 。

通過CAS 和自旋的配合可以一定程度上提高同步代碼的性能。

state 以 volatile 類型修飾,可以在多線程之間提供可見性。

ReentrantLock 和 CountDownLatch 對 state 的訪問方式分為獨(dú)占和共享兩種,本文雖然沒有解析 CountDownLatch 的源碼,但通過上面源碼的分析,可以想到其大致實(shí)現(xiàn)流程。

總結(jié)

以上是生活随笔為你收集整理的Java 多线程 —— AQS 详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。