java队列加锁_java并发-----浅析ReentrantLock加锁,解锁过程,公平锁非公平锁,AQS入门,CLH同步队列...
前言
為什么需要去了解AQS,AQS,AbstractQueuedSynchronizer,即隊列同步器。它是構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),JUC并發(fā)包的作者(Doug Lea)期望它能夠成為實現(xiàn)大部分同步需求的基礎(chǔ)。它是JUC并發(fā)包中的核心基礎(chǔ)組件
本文所有源碼基于JDK9
目的:掌握大概的流程/框架
適用人群:初學(xué)者想了解些源碼的
不適合:想深入了解的
ReentrantLock-非公平鎖
我們在實際中一定會用到ReentrantLock的lock操作,那么它的實現(xiàn)究竟是怎樣的?我們以重入鎖作為切入點。
1、構(gòu)建鎖,獲得鎖對象
//鎖的聲明
private final Sync sync;
// 構(gòu)造鎖,默認非公平
public ReentrantLock() {
sync = new NonfairSync();
}
2、lock方法,調(diào)用sync這個鎖對象
public void lock() {
sync.acquire(1);
}
// 來自AQS
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
關(guān)鍵性的四個方法:
1、tryAcquire:去嘗試獲取鎖,獲取成功則設(shè)置鎖狀態(tài)并返回true,否則返回false。該方法自定義同步組件自己實現(xiàn),該方法必須要保證線程安全的獲取同步狀態(tài)。
2、addWaiter:如果tryAcquire返回FALSE(獲取同步狀態(tài)失敗),則調(diào)用該方法將當(dāng)前線程加入到CLH同步隊列尾部。
3、acquireQueued:當(dāng)前線程會根據(jù)公平性原則來進行阻塞等待(自旋),直到獲取鎖為止;并且返回當(dāng)前線程在等待過程中有沒有中斷過。
4、selfInterrupt:產(chǎn)生一個中斷。
tryAcquire
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
加鎖,是通過NonfairSync的這個方法實現(xiàn)的,但是NofairSync并沒有它的實際代碼。真正實現(xiàn)的是它的父類Sync。
nonfairTryAcquire(int acquires)
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) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
getState(),這個方法在AQS中,用戶獲取鎖的狀態(tài)值。
/**
* Returns the current value of synchronization state.
* This operation has memory semantics of a {@code volatile} read.
*@return current state value
*/
protected final int getState() {
return state;
}
下面來解釋一下state在獨占鎖的作用是什么,結(jié)合下nonfairTryAcquire(int acquires)。
首先第一個if條件,state = 0 的時候代表著,當(dāng)前鎖沒有被某個線程占用,然后通過CAS操作設(shè)置線程的狀態(tài)為值1,并且把當(dāng)前線程設(shè)置為獨占鎖的擁有者。
第二個if,當(dāng)state!=0的時候,代表這個鎖已經(jīng)被別的線程占著了,就判斷,這個是不是這個鎖的擁有者,如果是的話,鎖state就+1,這就巧妙地設(shè)置了重入鎖的。每次+1的機制
CLH同步隊列
在介紹addWaiter前,先來看一下CLH同步隊列,就看著大佬們在說CLH但是少有人說他是啥,我解釋一下啊
The wait queue is a variant of a “CLH” (Craig, Landin, and Hagersten) lock queue. CLH locks are normally used for spinlocks
1、名稱:CLH 由三個人名字組成 (Craig, Landin, and Hagersten)
2、基本數(shù)據(jù)結(jié)構(gòu):基于FIFO雙端鏈表
3、用途:用于等待資源釋放的隊列。也就是等待鎖釋放的隊列
注意:由于筆者是初學(xué)者,覺得CLH隊列中的等待狀態(tài)轉(zhuǎn)換略微復(fù)雜,故意跳過。只看它們的方法,并沒有特別深入
addWaiter
/**
* Creates and enqueues node for current thread and given mode.
*
*@param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
*@return the new node
*/
private Node addWaiter(Node mode) {
Node node = new Node(mode);
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
node.setPrevRelaxed(oldTail);
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return node;
}
} else {
initializeSyncQueue();
}
}
}
與JDK8不同的地方是,addWaiter直接使用自旋(無限循環(huán))去完成入隊的操作,而不是調(diào)用enq,實現(xiàn)的內(nèi)容和enq差不多。下面給出enq的代碼
/**
* Inserts node into queue, initializing if necessary. See picture above.
*@param node the node to insert
*@return node's predecessor
*/
private Node enq(Node node) {
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
node.setPrevRelaxed(oldTail);
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return oldTail;
}
} else {
initializeSyncQueue();
}
}
}
注意addWaiter的返回值,是新加入的節(jié)點下面有用
acquireQueued
final boolean acquireQueued(final Node node, int arg) {
try {
// 中斷標(biāo)識
boolean interrupted = false;
// 自旋
for (;;) {
final Node p = node.predecessor();
// 如果當(dāng)前線程節(jié)點的前驅(qū)節(jié)點是頭結(jié)點,并且嘗試獲得鎖成功
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
// 如果獲取鎖失敗了,就進入掛起。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
關(guān)于掛起和喚醒,就先不看了,主要還是理解下流程。過多的細節(jié)會拖慢新手的學(xué)習(xí)之路,一定要記住這個!!
RenentrantLock-公平鎖
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
公平鎖和非公平鎖在于第一個判斷條件中的,hasQueuedPredecessors()這個方法。
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// 頭節(jié)點不是尾節(jié)點
// 第一個節(jié)點不為空
// 當(dāng)前節(jié)點是頭節(jié)點
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
從源碼我們可以驗證,公平和非公平的標(biāo)準(zhǔn)是否是按照隊列的順序進行鎖的獲取的原理。
小結(jié):
到這里,加鎖基本上就是結(jié)束了,我們忽略了掛起和喚醒這種復(fù)雜的操作。加鎖的操作中,阻塞隊列是由AQS使用CLH進行維護的。ReentrantLock,的同步操作主要還是依賴于AQS。
釋放鎖
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
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;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
調(diào)用Sync的tryRelease,減小state值,然后uppark進行喚醒下一個節(jié)點PS,筆者忽略了喚醒的操作。
總結(jié)
本文針對和我一樣剛?cè)腴T不久的新手,從整體上以重入鎖的加鎖和解鎖,去了解了一些AQS的CLH隊列的基本內(nèi)容,未涉及深層次,順便看了下公平和非公平的實現(xiàn)。算作是一種了解把,個人感覺太細節(jié)的東西,新手看了也沒用。還是從宏觀上把握把握,會用,然后了解點源碼,方便以后再來學(xué)習(xí)。
總結(jié)
以上是生活随笔為你收集整理的java队列加锁_java并发-----浅析ReentrantLock加锁,解锁过程,公平锁非公平锁,AQS入门,CLH同步队列...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android-短信验证功能,Andro
- 下一篇: 安徽工业大学计算机考研历年分数线,安徽工