Java并發(fā)編程之ReentrantLock解析
ReentrantLock
前篇寫了JUC的基礎(chǔ)AQS,其中介紹了它提供的很多模板方法,但是在實際編程中我們不會直接使用它,而是會使用它的各種實現(xiàn)。
本文將介紹在實際使用中出現(xiàn)頻率很高的一種并發(fā)鎖——ReentrantLock。
從名字上來看,ReentrantLock具有兩個特性,一個是可重入,一個是鎖。
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
...
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* 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);
}
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);
}
/**
* 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;
}
}
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
}ReentrantLock內(nèi)容定義了一個抽象類Sync,繼承自AQS,而不是自己去繼承AQS,所有對ReentrantLock的操作都會轉(zhuǎn)化為對Sync的操作。同時又定義了Sync的兩個子類FairSync和NonfairSync,分別用于實現(xiàn)公平鎖和非公平鎖。除非你在生成ReentrantLock時顯示的指明需要公平鎖,否則默認(rèn)采用非公平鎖。
可重入
先來看下可重入如何實現(xiàn),以默認(rèn)的非公平鎖舉例??芍厝?,意味著線程在獲取鎖之后,還可以再次獲取鎖,同樣,獲取了多少次,就要釋放多少次,否則資源釋放不對,別的線程將永遠(yuǎn)無法獲得鎖。
final void lock() {
//1、CAS將state置為1
if (compareAndSetState(0, 1))
//2、設(shè)置自己為獨占線程
setExclusiveOwnerThread(Thread.currentThread());
else
//3、否則嘗試獲取資源
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//1、如果state為0,說明當(dāng)前鎖沒有被占用
if (c == 0) {
//2、CAS嘗試將state設(shè)為1
if (compareAndSetState(0, acquires)) {
//3、獲取鎖成功,設(shè)置自己為獨占線程
setExclusiveOwnerThread(current);
return true;
}
}
//4、如果持有當(dāng)前鎖的線程就是自己
else if (current == getExclusiveOwnerThread()) {
//5、那么將state增加acquires
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//6、更新state,因為鎖已經(jīng)被自己持有了,所以這里不需要CAS設(shè)置
setState(nextc);
return true;
}
return false;
}
//AQS
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}獲取鎖的過程中,4、5、6三步即實現(xiàn)了可重入獲取。再看下釋放。
protected final boolean tryRelease(int releases) {
//1、每次釋放,將state減去釋放數(shù)
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//2、如果state為0,說明所有資源都已經(jīng)釋放
if (c == 0) {
free = true;
//3、將獨占線程置空
setExclusiveOwnerThread(null);
}
//4、更新state
setState(c);
return free;
}同樣,釋放時需要將獲取的資源依次扣除,什么時候state減為0了,才算該線程持有的所有資源都釋放掉了。
公平鎖實現(xiàn)可重入同理,不再贅述。
公平與非公平
那么公平鎖與非公平鎖又有什么區(qū)別呢?
非公平鎖的獲取上面已經(jīng)分析了,來看下公平鎖的獲取,看下有什么不同。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//1、等待隊列中是否已經(jīng)有節(jié)點在等待獲取鎖了
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;
}
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
//2、等待隊列以后已經(jīng)初始化,并且有別的線程正在入隊(enq)或者已經(jīng)入隊
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}公平和非公平的區(qū)別就在于,嘗試獲取鎖前要看下是否有線程已經(jīng)在自己之前就開始等待了,如果沒有才去競爭。通過這種方式保證公平,即先等先得。
公平鎖和非公平鎖哪種性能更好呢,公平鎖雖然能保證等待最久的線程可以先獲得鎖,但是這同時也以為著每次都會是不同的線程獲取鎖,每次都要進(jìn)行線程切換。《Java并發(fā)編程的藝術(shù)》進(jìn)行了測試,表明非公平鎖雖然可能造成某些線程一直獲取不到鎖,但是可以提高整體的吞吐量,所以ReentrantLock將其作為了默認(rèn)實現(xiàn)。如果是需要保證這種先等先得的特性,也可以使用公平鎖。
與synchronized對比
ReentrantLock在加鎖和內(nèi)存語義上提供了與synchronized相同的功能,此外還提供了定時、可中斷、公平性等特性。
JDK5時,ReentrantLock的性能要遠(yuǎn)優(yōu)于synchronized,但是JDK6引入了synchronized的鎖優(yōu)化,兩者之間的差別并沒有那么大了。
除非需要一些高級功能,如可定時、可輪詢、可中斷、公平性等,才使用ReentrantLock,否則應(yīng)該優(yōu)先使用synchronized,畢竟大部分人都用習(xí)慣了,而且使用簡單。
到此這篇關(guān)于Java并發(fā)編程之ReentrantLock解析的文章就介紹到這了,更多相關(guān)Java的ReentrantLock內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實現(xiàn)一個接口調(diào)取另一個接口(接口一調(diào)取接口二)
這篇文章主要介紹了java實現(xiàn)一個接口調(diào)取另一個接口(接口一調(diào)取接口二),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
簡單了解Spring Cloud Alibaba相關(guān)知識
這篇文章主要介紹了簡單了解Spring Cloud Alibaba相關(guān)知識,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-10-10
使用IDEA異常斷點來定位java.lang.ArrayStoreException的問題
這篇文章主要介紹了使用IDEA異常斷點來定位java.lang.ArrayStoreException的問題,平常開發(fā)過程中面對這種描述不夠清楚,無法定位具體原因的問題該如何處理,下面我們來一起學(xué)習(xí)一下吧2019-06-06
java連接mysql數(shù)據(jù)庫及測試是否連接成功的方法
這篇文章主要介紹了java連接mysql數(shù)據(jù)庫及測試是否連接成功的方法,結(jié)合完整實例形式分析了java基于jdbc連接mysql數(shù)據(jù)庫并返回連接狀態(tài)的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-09-09
Java實現(xiàn)將word轉(zhuǎn)換為html的方法示例【doc與docx格式】
這篇文章主要介紹了Java實現(xiàn)將word轉(zhuǎn)換為html的方法,結(jié)合實例形式分析了java針對doc與docx格式文件的相關(guān)轉(zhuǎn)換操作技巧,需要的朋友可以參考下2019-03-03
Jackson反序列化@JsonFormat 不生效的解決方案
這篇文章主要介紹了Jackson反序列化@JsonFormat 不生效的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08

