簡(jiǎn)書(shū) 占小狼
轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處狐援,謝謝钢坦!
前言
java5之后,并發(fā)包中新增了Lock接口(以及相關(guān)實(shí)現(xiàn)類(lèi))用來(lái)實(shí)現(xiàn)鎖的功能啥酱,它提供了與synchronized關(guān)鍵字類(lèi)似的同步功能爹凹。既然有了synchronized這種內(nèi)置的鎖功能,為何要新增Lock接口镶殷?先來(lái)想象一個(gè)場(chǎng)景:手把手的進(jìn)行鎖獲取和釋放禾酱,先獲得鎖A,然后再獲取鎖B,當(dāng)獲取鎖B后釋放鎖A同時(shí)獲取鎖C宇植,當(dāng)鎖C獲取后,再釋放鎖B同時(shí)獲取鎖D埋心,以此類(lèi)推指郁,這種場(chǎng)景下,synchronized關(guān)鍵字就不那么容易實(shí)現(xiàn)了拷呆,而使用Lock卻顯得容易許多闲坎。
synchronized不了解的可以看看這篇干貨 深入淺出synchronized
定義
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
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;
}
}
//默認(rèn)非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
//fair為false時(shí),采用公平鎖策略
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() {
sync.lock();
}
public void unlock() { sync.release(1);}
public Condition newCondition() {
return sync.newCondition();
}
...
}
從源代碼可以Doug lea巧妙的采用組合模式把lock和unlock方法委托給同步器完成茬斧。
使用方式
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
while(條件判斷表達(dá)式) {
condition.wait();
}
// 處理邏輯
} finally {
lock.unlock();
}
需要顯示的獲取鎖腰懂,并在finally塊中顯示的釋放鎖,目的是保證在獲取到鎖之后项秉,最終能夠被釋放绣溜。
在深入理解ReentrantLock的實(shí)現(xiàn)原理之前,我們先了解一下java同步器娄蔼。深入淺出java同步器
非公平鎖實(shí)現(xiàn)
在非公平鎖中怖喻,每當(dāng)線(xiàn)程執(zhí)行l(wèi)ock方法時(shí),都嘗試?yán)肅AS把state從0設(shè)置為1岁诉。
那么Doug lea是如何實(shí)現(xiàn)鎖的非公平性呢锚沸?
我們假設(shè)這樣一個(gè)場(chǎng)景:
- 持有鎖的線(xiàn)程A正在running,隊(duì)列中有線(xiàn)程BCDEF被掛起并等待被喚醒涕癣;
- 在某一個(gè)時(shí)間點(diǎn)塑煎,線(xiàn)程A執(zhí)行unlock瓦灶,喚醒線(xiàn)程B;
- 同時(shí)線(xiàn)程G執(zhí)行l(wèi)ock,這個(gè)時(shí)候會(huì)發(fā)生什么磕谅?線(xiàn)程B和G擁有相同的優(yōu)先級(jí),這里講的優(yōu)先級(jí)是指獲取鎖的優(yōu)先級(jí)南蓬,同時(shí)執(zhí)行CAS指令競(jìng)爭(zhēng)鎖芭商。如果恰好線(xiàn)程G成功了,線(xiàn)程B就得重新掛起等待被喚醒须蜗。
通過(guò)上述場(chǎng)景描述硅确,我們可以看書(shū),即使線(xiàn)程B等了很長(zhǎng)時(shí)間也得和新來(lái)的線(xiàn)程G同時(shí)競(jìng)爭(zhēng)鎖明肮,如此的不公平菱农。
static final class NonfairSync extends Sync {
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
下面我們用線(xiàn)程A和線(xiàn)程B來(lái)描述非公平鎖的競(jìng)爭(zhēng)過(guò)程。
- 線(xiàn)程A和B同時(shí)執(zhí)行CAS指令柿估,假設(shè)線(xiàn)程A成功循未,線(xiàn)程B失敗,則表明線(xiàn)程A成功獲取鎖,并把同步器中的exclusiveOwnerThread設(shè)置為線(xiàn)程A的妖。
- 競(jìng)爭(zhēng)失敗的線(xiàn)程B绣檬,在nonfairTryAcquire方法中,會(huì)再次嘗試獲取鎖嫂粟,Doug lea會(huì)在多處嘗試重新獲取鎖娇未,應(yīng)該是在這段時(shí)間如果線(xiàn)程A釋放鎖,線(xiàn)程B就可以直接獲取鎖而不用掛起星虹。完整的執(zhí)行流程如下:
同步器那塊的邏輯在深入淺出java同步器一文中已經(jīng)講解的很清楚零抬。
公平鎖實(shí)現(xiàn)
在公平鎖中,每當(dāng)線(xiàn)程執(zhí)行l(wèi)ock方法時(shí)宽涌,如果同步器的隊(duì)列中有線(xiàn)程在等待平夜,則直接加入到隊(duì)列中。
場(chǎng)景分析:
- 持有鎖的線(xiàn)程A正在running卸亮,對(duì)列中有線(xiàn)程BCDEF被掛起并等待被喚醒忽妒;
- 線(xiàn)程G執(zhí)行l(wèi)ock,隊(duì)列中有線(xiàn)程BCDEF在等待兼贸,線(xiàn)程G直接加入到隊(duì)列的對(duì)尾锰扶。
所以每個(gè)線(xiàn)程獲取鎖的過(guò)程是公平的,等待時(shí)間最長(zhǎng)的會(huì)最先被喚醒獲取鎖寝受。
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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í)現(xiàn)
重入鎖坷牛,即線(xiàn)程可以重復(fù)獲取已經(jīng)持有的鎖。在非公平和公平鎖中很澄,都對(duì)重入鎖進(jìn)行了實(shí)現(xiàn)京闰。
if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
條件變量Condition
條件變量很大一個(gè)程度上是為了解決Object.wait/notify/notifyAll難以使用的問(wèn)題。
public class ConditionObject implements Condition, java.io.Serializable {
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
public final void signal() {}
public final void signalAll() {}
public final void awaitUninterruptibly() {}
public final void await() throws InterruptedException {}
}
- Synchronized中甩苛,所有的線(xiàn)程都在同一個(gè)object的條件隊(duì)列上等待蹂楣。而ReentrantLock中,每個(gè)condition都維護(hù)了一個(gè)條件隊(duì)列讯蒲。
- 每一個(gè)Lock可以有任意數(shù)據(jù)的Condition對(duì)象痊土,Condition是與Lock綁定的,所以就有Lock的公平性特性:如果是公平鎖墨林,線(xiàn)程為按照FIFO的順序從Condition.await中釋放赁酝,如果是非公平鎖,那么后續(xù)的鎖競(jìng)爭(zhēng)就不保證FIFO順序了旭等。
- Condition接口定義的方法酌呆,await對(duì)應(yīng)于Object.wait,signal對(duì)應(yīng)于Object.notify搔耕,signalAll對(duì)應(yīng)于Object.notifyAll隙袁。特別說(shuō)明的是Condition的接口改變名稱(chēng)就是為了避免與Object中的wait/notify/notifyAll的語(yǔ)義和使用上混淆。
先看一個(gè)condition在生產(chǎn)者消費(fèi)者的應(yīng)用場(chǎng)景:
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by j_zhan on 2016/7/13.
*/
public class Queue<T> {
private final T[] items;
private final Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
private int head, tail, count;
public Queue(int maxSize) {
items = (T[]) new Object[maxSize];
}
public Queue() {
this(10);
}
public void put(T t) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
//數(shù)組滿(mǎn)時(shí),線(xiàn)程進(jìn)入等待隊(duì)列掛起菩收。線(xiàn)程被喚醒時(shí)梨睁,從這里返回。
notFull.await();
}
items[tail] = t;
if (++tail == items.length) {
tail = 0;
}
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await();
}
T o = items[head];
items[head] = null;//GC
if (++head == items.length) {
head = 0;
}
--count;
notFull.signal();
return o;
} finally {
lock.unlock();
}
}
}
假設(shè)線(xiàn)程AB在并發(fā)的往items中插入數(shù)據(jù)娜饵,當(dāng)items中元素存滿(mǎn)時(shí)而姐。如果線(xiàn)程A獲取到鎖,繼續(xù)添加數(shù)據(jù)划咐,滿(mǎn)足count == items.length條件,導(dǎo)致線(xiàn)程A執(zhí)行await方法钧萍。
ReentrantLock是獨(dú)占鎖褐缠,同一時(shí)刻只有一個(gè)線(xiàn)程能獲取到鎖,所以在lock.lock()和lock.unlock()之間可能有一次釋放鎖的操作(同樣也必然還有一次獲取鎖的操作)风瘦。在Quene類(lèi)中队魏,不管take還是put,在線(xiàn)程持有鎖之后只有await()方法有可能釋放鎖万搔,然后掛起線(xiàn)程胡桨,一旦條件滿(mǎn)足就被喚醒,再次獲取鎖瞬雹。具體實(shí)現(xiàn)如下:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
await實(shí)現(xiàn)邏輯:
- 將線(xiàn)程A加入到條件等待隊(duì)列中昧谊,如果最后一個(gè)節(jié)點(diǎn)是取消狀態(tài),則從對(duì)列中刪除酗捌。
- 線(xiàn)程A釋放鎖呢诬,實(shí)質(zhì)上是線(xiàn)程A修改AQS的狀態(tài)state為0,并喚醒AQS等待隊(duì)列中的線(xiàn)程B胖缤,線(xiàn)程B被喚醒后尚镰,嘗試獲取鎖,接下去的過(guò)程就不重復(fù)說(shuō)明了哪廓。
- 線(xiàn)程A釋放鎖并喚醒線(xiàn)程B之后狗唉,如果線(xiàn)程A不在AQS的同步隊(duì)列中,線(xiàn)程A將通過(guò)LockSupport.park進(jìn)行掛起操作涡真。
- 隨后分俯,線(xiàn)程A等待被喚醒,當(dāng)線(xiàn)程A被喚醒時(shí)哆料,會(huì)通過(guò)acquireQueued方法競(jìng)爭(zhēng)鎖澳迫,如果失敗,繼續(xù)掛起剧劝。如果成功橄登,線(xiàn)程A從await位置恢復(fù)。
假設(shè)線(xiàn)程B獲取鎖之后,執(zhí)行了take操作和條件變量的signal拢锹,signal通過(guò)某種實(shí)現(xiàn)喚醒了線(xiàn)程A谣妻,具體實(shí)現(xiàn)如下:
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ((firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node); //線(xiàn)程A插入到AQS的等待隊(duì)列中
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
signal實(shí)現(xiàn)邏輯:
- 接著上述場(chǎng)景,線(xiàn)程B執(zhí)行了signal方法卒稳,取出條件隊(duì)列中的第一個(gè)非CANCELLED節(jié)點(diǎn)線(xiàn)程蹋半,即線(xiàn)程A。另外充坑,signalAll就是喚醒條件隊(duì)列中所有非CANCELLED節(jié)點(diǎn)線(xiàn)程减江。遇到CANCELLED線(xiàn)程就需要將其從隊(duì)列中刪除。
- 通過(guò)CAS修改線(xiàn)程A的waitStatus為0捻爷,表示該節(jié)點(diǎn)已經(jīng)不是等待條件狀態(tài)辈灼,并將線(xiàn)程A插入到AQS的等待隊(duì)列中。
- 喚醒線(xiàn)程A也榄,線(xiàn)程A和別的線(xiàn)程進(jìn)行鎖的競(jìng)爭(zhēng)巡莹。
總結(jié)
- ReentrantLock提供了內(nèi)置鎖類(lèi)似的功能和內(nèi)存語(yǔ)義。
- 此外甜紫,ReetrantLock還提供了其它功能降宅,包括定時(shí)的鎖等待、可中斷的鎖等待囚霸、公平性腰根、以及實(shí)現(xiàn)非塊結(jié)構(gòu)的加鎖、Condition拓型,對(duì)線(xiàn)程的等待和喚醒等操作更加靈活唠雕,一個(gè)ReentrantLock可以有多個(gè)Condition實(shí)例,所以更有擴(kuò)展性吨述,不過(guò)ReetrantLock需要顯示的獲取鎖岩睁,并在finally中釋放鎖,否則后果很?chē)?yán)重揣云。
- ReentrantLock在性能上似乎優(yōu)于Synchronized捕儒,其中在jdk1.6中略有勝出,在1.5中是遠(yuǎn)遠(yuǎn)勝出邓夕。那么為什么不放棄內(nèi)置鎖刘莹,并在新代碼中都使用ReetrantLock?
- 在java1.5中焚刚, 內(nèi)置鎖與ReentrantLock相比有例外一個(gè)優(yōu)點(diǎn):在線(xiàn)程轉(zhuǎn)儲(chǔ)中能給出在哪些調(diào)用幀中獲得了哪些鎖点弯,并能夠檢測(cè)和識(shí)別發(fā)生死鎖的線(xiàn)程。Reentrant的非塊狀特性任然意味著矿咕,獲取鎖的操作不能與特定的棧幀關(guān)聯(lián)起來(lái)抢肛,而內(nèi)置鎖卻可以狼钮。
- 因?yàn)閮?nèi)置鎖時(shí)JVM的內(nèi)置屬性,所以未來(lái)更可能提升synchronized而不是ReentrantLock的性能捡絮。例如對(duì)線(xiàn)程封閉的鎖對(duì)象消除優(yōu)化熬芜,通過(guò)增加鎖粒度來(lái)消除內(nèi)置鎖的同步。
END福稳。
我是占小狼涎拉。
在魔都艱苦奮斗,白天是上班族的圆,晚上是知識(shí)服務(wù)工作者鼓拧。
讀完我的文章有收獲,記得關(guān)注和點(diǎn)贊哦越妈,如果非要打賞季俩,我也是不會(huì)拒絕的啦!