日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
從ReentrantLock角度解析AQS

一、概述

閑來不卷,隨便聊一點(diǎn)。

創(chuàng)新互聯(lián)建站主要從事網(wǎng)頁設(shè)計(jì)、PC網(wǎng)站建設(shè)(電腦版網(wǎng)站建設(shè))、wap網(wǎng)站建設(shè)(手機(jī)版網(wǎng)站建設(shè))、自適應(yīng)網(wǎng)站建設(shè)、程序開發(fā)、網(wǎng)站優(yōu)化、微網(wǎng)站、小程序設(shè)計(jì)等,憑借多年來在互聯(lián)網(wǎng)的打拼,我們在互聯(lián)網(wǎng)網(wǎng)站建設(shè)行業(yè)積累了豐富的成都網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、網(wǎng)站設(shè)計(jì)、網(wǎng)絡(luò)營銷經(jīng)驗(yàn),集策劃、開發(fā)、設(shè)計(jì)、營銷、管理等多方位專業(yè)化運(yùn)作于一體。

一般情況下,大家系統(tǒng)中至少也是JDK8了,那想必對于JDK5加入的一系列功能并不陌生吧。那時候重點(diǎn)加入了java.util.concurrent并發(fā)包,我們簡稱JUC。JUC下提供了很多并發(fā)編程實(shí)用的工具類,比如并發(fā)鎖lock、原子操作atomic、線程池操作Executor等等。下面,我對JUC做了整理,大致分為下面幾點(diǎn):

基于JDK8,今天重點(diǎn)來聊下JUC并發(fā)包下的一個類,AbstractQueuedSynchronizer。

首先,淺顯的從名字上看,抽象的隊(duì)列同步器;實(shí)際上,這名字也跟它的作用如出一轍。抽象,即需要被繼承;隊(duì)列同步器,其內(nèi)部維護(hù)了一個隊(duì)列,供線程入隊(duì)等待;最終實(shí)現(xiàn)多個線程訪問共享資源的功能。

二、源碼解析

進(jìn)入AbstractQueuedSynchronizer內(nèi)部,需要掌握三個重要的屬性:

private transient volatile Node head;

private transient volatile Node tail;

private volatile int state;
  • head:標(biāo)記等待隊(duì)列頭部節(jié)點(diǎn)。
  • tail:標(biāo)記等待隊(duì)列尾部節(jié)點(diǎn)。
  • state:線程的鎖定狀態(tài);state=0,表示資源未被上鎖;state>0,表示資源被上鎖

我們調(diào)試AQS的源碼,必須尋找一個源碼調(diào)試的切入點(diǎn),我這里用我們并發(fā)編程常用的Lock鎖作為調(diào)試AQS的切入點(diǎn),因?yàn)檫@是解決線程安全問題常用的手段之一。

2.1、源碼的切入點(diǎn)

AQS的源碼調(diào)試,從Lock接口出發(fā),JDK源碼定義如下:

public interface Lock {

void lock();

void lockInterruptibly() throws InterruptedException;

boolean tryLock();

boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

void unlock();

Condition newCondition();
}

從源碼中看到,Lock?是一個接口,所以該接口會有一些實(shí)現(xiàn)類,其中有一個實(shí)現(xiàn)類ReentrantLock,可重入鎖,想必大家都不會陌生。

2.2、ReentrantLock的lock方法

通過跟蹤源碼可以看到,ReentrantLock#lock內(nèi)部實(shí)現(xiàn)貌似比較簡單,只有簡短的一行代碼

public void lock() {
sync.lock();
}

其實(shí)內(nèi)部是維護(hù)了一個Sync?的抽象類,調(diào)用的是Sync的lock()方法。

abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;

abstract void lock();

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;
}

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;
}

protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
// ...
}

可以看到,Sync?也是個抽象類,它有兩個實(shí)現(xiàn)類:NonfairSync和FairSync?,這里其實(shí)就引出了我們今天的主角,AbstractQueuedSynchronizer,Sync繼承了它。

static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;

final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;

final void lock() {
acquire(1);
}

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;
}
}

下面我整理了這一系列類的UML圖

通過類圖可知,lock()方法最終調(diào)用的是ReentrantLock?類下,內(nèi)部類NonfairSync或FairSync?的lock方法;對于這兩個類,前者叫非公平鎖,后者叫公平鎖。通過ReentrantLock?的構(gòu)造器可知,默認(rèn)使用NonfairSync類。

public ReentrantLock() {
sync = new NonfairSync();
}

從NonfairSync類的lock方法出發(fā),引出第一個AQS下的方法compareAndSetState。

final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

從compareAndSetState方法的命名可以發(fā)現(xiàn),就是比較并交換的意思,典型的CAS無鎖機(jī)制。

protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

我們可以觀察到,這里其實(shí)調(diào)用的是Unsafe類的compareAndSwapInt方法,傳入的expect為0,update為1;意思是如果當(dāng)前值為0,那我就把值最終更新為1。

Unsafe?這個類下面,發(fā)現(xiàn)好多方法都是用native?這個關(guān)鍵詞進(jìn)行修飾的(也包括compareAndSwapInt方法),用native關(guān)鍵詞修飾的方法,表示原生的方法;原生方法的實(shí)現(xiàn)并不是Java語言,最終實(shí)現(xiàn)是C/C++;這并不是本文的討論范圍。

回到AQS的compareAndSetState方法,返回值是boolean類型,true表示值更新為1成功,false表示不成功。這里出現(xiàn)兩個分支,成功,走setExclusiveOwnerThread方法;不成功,走acquire方法。咱優(yōu)先討論acquire方法。

2.3、AQS的acquire方法

先來看一下該方法的源碼;

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

這里的核心是兩個方法,tryAcquire方法和acquireQueued方法。首先會調(diào)用tryAcquire()方法,看方法命名是嘗試獲??;實(shí)際上這個方法確實(shí)就在做一件事“嘗試獲取資源”。

protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}

不過AQS中的這個方法是protected?修飾,并沒有去實(shí)現(xiàn),僅僅只是預(yù)留了方法入口,后期需要由其子類去實(shí)現(xiàn);這里的子類就是上文中的NonfairSync類,該類的源碼在上文中已經(jīng)貼出。這段源碼其實(shí)運(yùn)用了我們常見的一個設(shè)計(jì)模式,“模板方法模式”。

2.4、NonfairSync的tryAcquire方法

NonfairSync的tryAcquire方法源碼如下

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}

這里并沒有直接去實(shí)現(xiàn)tryAcquire方法,而是調(diào)用了Sync類下的nonfairTryAcquire方法。

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中的state字段,這個字段就是多個線程搶占的共享資源,所以這個字段很重要;volatile?關(guān)鍵字修飾,保證內(nèi)存的可見性,int類型,對于ReentrantLock鎖而言,當(dāng)state=0時,表示無鎖,當(dāng)state>0時,表示資源已被線程鎖定。

下面分析下這段代碼:

  • 如果state=0表示無鎖,通過cas去更新state的值,這里更新為1。
  • 將持有鎖的線程更新為當(dāng)前線程。
  • 如果上述cas未更新成功,或者state!=0,表示已上鎖。
  • 繼續(xù)判斷下持有鎖的線程如果是當(dāng)前線程,state字段做疊加,這里表示ReentrantLock的含義,表示可重入鎖。
  • 最后,state!=0,持有鎖的線程也不是當(dāng)前線程,表示不能對資源加鎖,返回false。

tryAcquire方法的判斷至此結(jié)束,不過最終的走向需要看它的返回值;返回true,表示當(dāng)前線程搶占到鎖,或者當(dāng)前線程就是搶占鎖的線程,直接重入,加鎖流程結(jié)束;返回false,表示沒有搶占到鎖,流程繼續(xù),這里就引出下個話題,CLH線程等待隊(duì)列。

2.5、AQS的addWaiter方法

2.5.1、CLH隊(duì)列

首先咱來看一段源碼中的注釋

The wait queue is a variant of a "CLH" (Craig, Landin, and Hagersten) lock queue. CLH locks are normally used for spinlocks

大致意思是:CLH隊(duì)列是由Craig、Landin、Hagersten這三位老哥名字的首字母疊加在一起命名的,它是一個等待隊(duì)列,它是一個變種隊(duì)列,用到了自旋。

這里的信息要抓住三點(diǎn):等待隊(duì)列、變種隊(duì)列、自旋。

2.5.2、Node類

在解析addWaiter方法實(shí)現(xiàn)之前,就不得不提到一個內(nèi)部類Node;addWaiter方法的入?yún)⑹沁@個類型,所以先來看看這個類。源碼如下:

static final class Node {

static final Node SHARED = new Node();

static final Node EXCLUSIVE = null;

static final int CANCELLED = 1;

static final int SIGNAL = -1;

static final int CONDITION = -2;

static final int PROPAGATE = -3;

volatile int waitStatus;

volatile Node prev;

volatile Node next;

volatile Thread thread;

Node nextWaiter;

final boolean isShared() {
return nextWaiter == SHARED;
}

final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}

Node() {
}

Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}

Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}

這里先大致介紹下,每個屬性的意思:

  • SHARED:類型就是Node,表示共享模式。
  • EXCLUSIVE:類型也是Node,表示獨(dú)占模式,這里的ReentrantLock就是獨(dú)占模式。
  • waitStatus:int類型,當(dāng)前Node節(jié)點(diǎn)下,存儲的線程狀態(tài)。
  • CANCELLED:int類型,等于1,waitStatus屬性的值之一,表示節(jié)點(diǎn)被取消狀態(tài)。
  • SIGNAL:int類型,等于-1,waitStatus屬性的值之一,表示當(dāng)前節(jié)點(diǎn)需要去喚醒下一個節(jié)點(diǎn)。
  • CONDITION:int類型,等于-2,waitStatus屬性的值之一,表示節(jié)點(diǎn)處于等待狀態(tài)。
  • PROPAGATE:int類型,等于-2,waitStatus屬性的值之一,表示下一個被獲取的對象應(yīng)該要無條件傳播,該值僅在共享模式下使用。
  • prev:Node類型,指向隊(duì)列中當(dāng)前節(jié)點(diǎn)的前一個節(jié)點(diǎn)。
  • next:Node類型,指向隊(duì)列中當(dāng)前節(jié)點(diǎn)的下一個節(jié)點(diǎn)。
  • thread:存儲當(dāng)前線程信息。
  • nextWaiter:用來存儲節(jié)點(diǎn)的指針,不過會出現(xiàn)兩種情況;等待隊(duì)列中,會將該屬性的值設(shè)置成SHARED或者EXCLUSIVE,用來區(qū)分當(dāng)前節(jié)點(diǎn)處于共享模式還是獨(dú)享模式;條件隊(duì)列中,用于存放下一個節(jié)點(diǎn)的指針,所以當(dāng)是條件隊(duì)列的情況下,這個隊(duì)列是單向隊(duì)列。
  • isShared():返回是否屬于共享模式,true表示共享模式,false表示獨(dú)享模式。
  • predecessor():獲取當(dāng)前節(jié)點(diǎn)的前一個節(jié)點(diǎn)。

另外,Node類還有兩個有參構(gòu)造器:從作者的注釋就能看出來,第一個構(gòu)造器是在等待隊(duì)列的時,創(chuàng)建節(jié)點(diǎn)使用,第二個構(gòu)造器是在條件隊(duì)列時,創(chuàng)建節(jié)點(diǎn)使用。

2.5.3、方法解析

private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}

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

其實(shí)這段方法是在創(chuàng)建Node對象,Node對象就是組成CLH隊(duì)列的基礎(chǔ)元素。

  • 創(chuàng)建一個Node對象,mode參數(shù)由上述的acquire()方法傳遞而來,可以看到傳入Node.EXCLUSIVE,表示獨(dú)占模式。
  • 判斷隊(duì)尾有指向節(jié)點(diǎn),剛創(chuàng)建的節(jié)點(diǎn)放入隊(duì)列的隊(duì)尾,并且通過cas將隊(duì)尾指針改成當(dāng)前創(chuàng)建節(jié)點(diǎn),最后返回當(dāng)前創(chuàng)建節(jié)點(diǎn)。
  • 如果隊(duì)尾沒有指向節(jié)點(diǎn),調(diào)用enq方法,做隊(duì)列的初始化操作。
  • 這里出現(xiàn)了第一個自旋,enq方法是無限循環(huán)的,就像作者注釋的一樣,Must initialize,必須初始化。
  • 這里先是重新new了一個新的node(也可以叫空節(jié)點(diǎn)),標(biāo)記它為隊(duì)列頭。
  • 隨后再將addWaiter方法中創(chuàng)建的node,加入到隊(duì)列尾。

總結(jié)下addWaiter方法干的事情:

  1. 創(chuàng)建一個節(jié)點(diǎn),存儲當(dāng)前線程,并標(biāo)記獨(dú)占模式。
  2. 判斷隊(duì)列是否為空,不為空,通過cas將存儲當(dāng)前線程的node節(jié)點(diǎn)加入到對尾,并且對該節(jié)點(diǎn)做對尾標(biāo)記。
  3. 隊(duì)列為空,通過自旋,做初始化操作。
  4. 初始化過后的隊(duì)列,隊(duì)列頭是一個空節(jié)點(diǎn),隊(duì)列尾是存儲當(dāng)前線程的節(jié)點(diǎn)。

2.6、AQS的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 GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

private void cancelAcquire(Node node) {
if (node == null)
return;

node.thread = null;

Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;

Node predNext = pred.next;

node.waitStatus = Node.CANCELLED;

if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}

從這個方法看到,又是運(yùn)用了無限循環(huán),需要分兩個步驟去觀察:1.當(dāng)前方法中的判斷,自己的上一個節(jié)點(diǎn)是否是頭部節(jié)點(diǎn)(頭部節(jié)點(diǎn)就是占用資源的節(jié)點(diǎn));2.當(dāng)前節(jié)點(diǎn)正式入隊(duì)列,并且被掛起。

2.6.1、acquireQueued方法中的判斷

當(dāng)前節(jié)點(diǎn)的前一個節(jié)點(diǎn)是隊(duì)列頭部,意味著當(dāng)前節(jié)點(diǎn)的前一個節(jié)點(diǎn),就是持有資源的節(jié)點(diǎn);當(dāng)資源被釋放,當(dāng)前節(jié)點(diǎn)會去嘗試爭奪鎖資源;如果拿到鎖資源,當(dāng)前節(jié)點(diǎn)會被標(biāo)記為隊(duì)列頭部節(jié)點(diǎn),它的上個節(jié)點(diǎn)(老的頭部節(jié)點(diǎn))會被置為null,需要被GC及時清除,所以作者在這里添加了一個注釋:help GC;下圖就是描述了這個流程:

2.6.2、shouldParkAfterFailedAcquire方法實(shí)現(xiàn)

如果當(dāng)前節(jié)點(diǎn)的上一個節(jié)點(diǎn),并不是頭部節(jié)點(diǎn);這里就需要用到上述Node類中介紹的各種狀態(tài)字段了;先來重點(diǎn)介紹下Node類中的兩個狀態(tài)屬性:

  • CANCELLED:int類型,等于1,waitStatus屬性的值之一,表示節(jié)點(diǎn)被取消。
  • SIGNAL:int類型,等于-1,waitStatus屬性的值之一,表示當(dāng)前節(jié)點(diǎn)需要去喚醒下一個節(jié)點(diǎn)。

進(jìn)入的shouldParkAfterFailedAcquire這個方法內(nèi)部,該方法接受兩個參數(shù):當(dāng)前節(jié)點(diǎn)前一個節(jié)點(diǎn)和當(dāng)前節(jié)點(diǎn)。首先,獲取上一個節(jié)點(diǎn)的waitStatus屬性,然后通過這個屬性做如下判斷:

  1. 如果狀態(tài)是SIGNAL(即等于-1),直接返回true,后續(xù)就會交給parkAndCheckInterrupt方法去將當(dāng)前線程掛起。
  2. 如果不是SIGNAL,對于當(dāng)前ReentrantLock而言,ws>0的操作是滿足的,所以下面的步驟就是當(dāng)前節(jié)點(diǎn)一直往前尋找,跳過已被標(biāo)記狀態(tài)為CANCELLED的節(jié)點(diǎn),直到找到狀態(tài)是SIGNAL的節(jié)點(diǎn),將該節(jié)點(diǎn)作為當(dāng)前節(jié)點(diǎn)的上一個節(jié)點(diǎn)。也印證了SIGNAL狀態(tài)的解釋:當(dāng)前節(jié)點(diǎn)的上一個節(jié)點(diǎn)是SIGNAL,那么當(dāng)前節(jié)點(diǎn)需要掛起,等待被喚醒。最后進(jìn)入下個循環(huán),直到上個節(jié)點(diǎn)狀態(tài)是SIGNAL,執(zhí)行上面的第一步,返回true。

這里可以想象成一個排隊(duì)去食堂打飯的場景,你在低頭玩手機(jī)前,跟你前面的同學(xué)說,我玩會手機(jī),快到了叫我一下;結(jié)果你前面的同學(xué)嫌隊(duì)伍長走了(CANCELLED狀態(tài)),所以你只能繼續(xù)找他的上一個同學(xué);直到有同學(xué)回答你,好的(該同學(xué)被標(biāo)記SIGNAL狀態(tài));然后你就低頭玩手機(jī),等待回答你“好的”的那個同學(xué)叫你。

  1. 最后compareAndSetWaitStatus方法其實(shí)不用看也知道,通過cas機(jī)制,將當(dāng)前節(jié)點(diǎn)的上一個節(jié)點(diǎn)的waitStatus修改成SIGNAL狀態(tài),這樣的話,當(dāng)前節(jié)點(diǎn)才能被掛起,等待喚醒。

再來看下parkAndCheckInterrupt這個方法

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

// LockSupport#park
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}

其中最終又是這個Unsafe類,通過它的原生方法park,去掛起當(dāng)前線程,這里就不展開贅述了。

2.7、資源上鎖總結(jié)

下面整理下從lock方法作為切入點(diǎn),一系列的調(diào)用:

2.8、ReentrantLock的unlock方法

之前一直在講資源“上鎖”,那么這個方法就是給資源解鎖。這里給出重要的部分源碼

// AQS中
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

// AQS中
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}

// ReentrantLock中
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;
}

2.9、ReentrantLock的tryRelease方法

在調(diào)用unlock方法去解鎖后,最終是調(diào)用AQS中的release方法去實(shí)現(xiàn)這個解鎖功能的;在該方法中,首先會調(diào)用ReentrantLock中的tryRelease方法,去做state狀態(tài)值的遞減操作。

  1. 首先,獲取state值(在AQS中有這個公共屬性,上文提到過),這里是對當(dāng)前state值減去1。
  2. 再判斷當(dāng)前解鎖的線程與持有鎖的線程是不是同一個,不是的話,直接拋異常。所以t線程占用鎖,只有t線程才能解鎖,解鈴還須系鈴人。
  3. 最后判斷做完遞減的值是不是等于0,如果為0,將持有鎖的線程清空,更新state字段為遞減值(這里是0),最后返回true,代表鎖已經(jīng)被釋放了。
  4. 如果不是0,更新state字段為遞減值(不是0),也不會清空持有鎖的線程,意味著資源還是被線程加鎖中,最后返回false。

2.10、AQS的release方法

在tryRelease方法返回false的時候,release方法并不會做任何操作,直接就結(jié)束了,意味著解鎖并沒有完成;但是在返回true的時候,具體分以下幾部操作:

  1. 拿到CLH隊(duì)列被標(biāo)記頭部的節(jié)點(diǎn)。
  2. 判斷不是空(隊(duì)列不能是空的),并且頭部節(jié)點(diǎn)的等待狀態(tài)不是0,在這種情況下,它只能是-1(SIGNAL),所以是需要去喚醒下個節(jié)點(diǎn)的。
  3. 最后,調(diào)用AQS中的unparkSuccessor方法,去喚醒線程。

2.11、AQS的unparkSuccessor方法

上面說到了,這個方法主要是用來喚醒線程的,下面還是做一下具體的解析:

  1. 該方法傳參是一個Node節(jié)點(diǎn),這里傳入的是被標(biāo)記隊(duì)列頭的節(jié)點(diǎn)(頭部節(jié)點(diǎn)是持有鎖資源的節(jié)點(diǎn))。
  2. 拿到頭部節(jié)點(diǎn)的waitStatus狀態(tài)屬性,并且判斷小于0的情況下(該情況是waitStatus=-1),通過cas機(jī)制將頭部節(jié)點(diǎn)的狀態(tài)改為0,初始化狀態(tài)。
  3. 拿到頭部節(jié)點(diǎn)的下個節(jié)點(diǎn),也就是真正意義上處于等待中的第一個節(jié)點(diǎn)。
  4. 它還是先判斷了這個拿到的節(jié)點(diǎn)是否為null,或者狀態(tài)大于0(亦或說判斷狀態(tài)等于1);如果條件成立,說明頭節(jié)點(diǎn)的下個節(jié)點(diǎn)是空,或者下個節(jié)點(diǎn)被取消了。
  5. 如果第四個判斷條件滿足,從隊(duì)尾一直從后往前找,找到離頭節(jié)點(diǎn)最近的那個節(jié)點(diǎn)。
  6. 通過Unsafe類的unpark原生方法去喚醒上述找到的,距離頭部節(jié)點(diǎn)最近的未處于取消狀態(tài)下的節(jié)點(diǎn)。

2.12、資源解鎖總結(jié)

通過上面的描述可以發(fā)現(xiàn),資源解鎖是相對簡單的;它只能被上鎖的線程去解鎖;通過遞減AQS內(nèi)部維護(hù)的state屬性值,直到state減為0,表示資源已被解鎖;當(dāng)資源被解鎖后,需要通過Unsafe的unpark方法,去喚醒CLH隊(duì)列中,被掛起的第一個節(jié)點(diǎn)上的線程。

2.13、公平鎖與非公平鎖的差異

在2.2中說過,當(dāng)我們使用無參構(gòu)造器創(chuàng)建一把“鎖”的時候,默認(rèn)是使用NonfairSync這個內(nèi)部類,也就是非公平鎖;但是在源碼中發(fā)現(xiàn)ReentrantLock?還存在一個有參構(gòu)造器,參數(shù)是一個boolean類型;

public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

很明顯,這種方式就是將選擇權(quán)交給開發(fā)人員,當(dāng)我們傳入true時,就會創(chuàng)建一把“公平鎖”。還是一樣,先來看下公平鎖的內(nèi)部;

static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;

final void lock() {
acquire(1);
}

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;
}
}

從源碼的角度,咱來看下,為什么一個叫“非公平鎖”,另一個叫“公平鎖”?

其實(shí)不難發(fā)現(xiàn),NonfairSync?內(nèi)部的lock方法,它是一上來就通過cas機(jī)制去搶占state公共資源,搶不到才去執(zhí)行acquire方法實(shí)現(xiàn)后續(xù)入隊(duì)列等一系列的操作;而這里FairSync的lock方法,它是直接執(zhí)行acquire方法,執(zhí)行后續(xù)的操作。等于非公平鎖,會去多爭取一次資源,對于在CLH隊(duì)列中等待的線程,是“不公平”的。

除了lock方法存在差異之外,在tryAcquire方法中,也存在著不同。FairSync類中,會多執(zhí)行hasQueuedPredecessors方法,它是AQS下的一個公用方法,下面具體看下這個方法;

public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}

只有簡短的幾行,卻有很多種可能性,但是整個方法主要功能就是判斷當(dāng)前線程是否需要入隊(duì)列:返回false,隊(duì)列為空,不對等待;返回true,隊(duì)列不是空,去排隊(duì)等待。下面需要重點(diǎn)講下這一行代碼:return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());

2.13.1、hasQueuedPredecessors返回false

返回false,情況也有兩種:1、h != t** **是false,2、h != t是true,并且 (s = h.next) == null 是false, s.thread != Thread.currentThread()是false。

第一種情況比較簡單,意思是頭結(jié)點(diǎn)和尾節(jié)點(diǎn)是同一個,說明隊(duì)列是空的,不需要排隊(duì)等待,所以直接返回false。

第二種情況,頭尾不是同一個節(jié)點(diǎn),頭部節(jié)點(diǎn)的下個節(jié)點(diǎn)也不是空,并且頭部節(jié)點(diǎn)的下一個節(jié)點(diǎn)就是當(dāng)前線程。其實(shí)就可以理解為,前面的資源剛釋放,正好輪到當(dāng)前線程來搶占資源,這種情況相對較少。

2.13.2、hasQueuedPredecessors返回true

返回true,有兩種情況:

h != t是true,并且 (s = h.next) == null 是true。

h != t是true,并且 (s = h.next) == null 是false, s.thread不等于Thread.currentThread()是true。

1、這里的頭尾不是同一個節(jié)點(diǎn)是必要滿足的條件,保證了隊(duì)列起碼不是空的。然后(s = h.next) == null 滿足是true,這里解釋起來就必須回顧下enq初始化隊(duì)列這個方法。

private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
網(wǎng)頁名稱:從ReentrantLock角度解析AQS
分享鏈接:http://www.dlmjj.cn/article/cosdigd.html