前言
在前面的文章已經(jīng)介紹了AQS, 接下來的幾篇文章將會(huì)介紹各種鎖, 而且這些鎖都是基于AQS的, 所以需要對AQS有一定的了解將會(huì)幫助我們更容易理解這些鎖. 本文分析的主題是重入鎖
ReentrantLock.
本文源代碼 : 源代碼下載
本文會(huì)以三步來進(jìn)行
ReentrantLock的分析.1. 先以一個(gè)小例子來解釋重入鎖的基本概念
2. 用UML畫出該類的結(jié)構(gòu)
3. 分析源碼并且用一個(gè)例子測試
例子1: 簡單理解重入鎖ReentrantLock
重入鎖顧名思義就是說在某一個(gè)線程獲得鎖再次去獲取鎖, 是被允許的, 例如當(dāng)遞歸調(diào)用一個(gè)
synchronized修飾的方法, 說明synchronized是可以重入的. 那在[Java源碼][并發(fā)J.U.C]---用代碼一步步實(shí)現(xiàn)AQS(1)---獨(dú)占鎖的獲取和釋放 中的鎖Mutex就不是重入鎖(Mutex類的代碼在代碼下載處可以下載), 因此就用這兩個(gè)鎖比較看一下重入鎖.
public class TestReentrantLock {
static Lock mutexLock = new Mutex();
static Lock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
//new Runner(mutexLock, "thread-1").start();
new Runner(reentrantLock, "thread-1").start();
}
static class Runner extends Thread {
Lock lock;
public Runner(Lock lock, String name) {
super(name);
this.lock = lock;
}
public void run() {
lock.lock();
System.out.println(Thread.currentThread().getName() + " get locks at the first time.");
lock.lock();
System.out.println(Thread.currentThread().getName() + " get locks at the second time.");
lock.unlock();
System.out.println(Thread.currentThread().getName() + " release locks at the first time.");
lock.unlock();
System.out.println(Thread.currentThread().getName() + " release locks at the second time.");
}
}
}
當(dāng)使用
ReentrantLock的時(shí)候會(huì)輸出如下內(nèi)容. 表明該鎖是可重入的.
thread-1 get locks at the first time.
thread-1 get locks at the second time.
thread-1 release locks at the first time.
thread-1 release locks at the second time.
當(dāng)使用
Mutex的時(shí)候會(huì)在輸出第一句話的時(shí)候,就一直阻塞了,表明該線程在第一次獲取鎖成功了,當(dāng)?shù)诙卧偃カ@取鎖的時(shí)候就一直阻塞了.
thread-1 get locks at the first time.
ReentrantLock 內(nèi)部分析
如下是整個(gè)
ReentrantLock類所包含的內(nèi)容,關(guān)于Condition的部分我沒有加進(jìn)來,因?yàn)樵诤罄m(xù)的博客中會(huì)有專門的一篇來分析該類.
ReentrantLock.png
如圖所示, 先看右側(cè),
ReentrantLock實(shí)現(xiàn)了Lock接口和Serializable接口. 因此就要實(shí)現(xiàn)Lock中的所有方法,也就是在圖中ReentrantLock方法框中. 它的方法主要分三類:
1. 構(gòu)造方法包括ReentrantLock()和ReentrantLock(boolean fair)兩個(gè).
2.Lock接口中需要實(shí)現(xiàn)的方法, 從Lock()到newCondition()方法.
3. 一些監(jiān)控方法,包括取出等待隊(duì)列中所有在等待的線程等等.從getHoldCount()開始到結(jié)尾.
另外
Lock接口需要實(shí)現(xiàn)的方法基本上都是借助Sync類來實(shí)現(xiàn)的,因此ReentrantLock類中有一個(gè)sync的成員變量, 該成員變量可以是Sync的兩個(gè)子類NonfairSync(非公平鎖)和FairSync(公平鎖)中的某一個(gè).
公平性: 再看左側(cè),
Sync繼承了AQS,并且實(shí)現(xiàn)了一些共有方法,并且留有抽象方法lock()交由子類各自實(shí)現(xiàn), 子類FairSync表示的是非公平鎖, 意思是如果在絕對時(shí)間上,先對鎖進(jìn)行獲取的請求一定先被滿足,那么這個(gè)鎖是公平的,否則該鎖就是不公平的,也就是NonfairSync類.
其實(shí)
ReentrantLock的核心就是Sync及其子類的實(shí)現(xiàn), 因?yàn)閷?shí)現(xiàn)Lock接口中的方法都是通過Sync類的實(shí)例來代理實(shí)現(xiàn)的. 所以接下來就具體看看Sync相關(guān)類的源碼實(shí)現(xiàn).
源碼分析
Sync的實(shí)現(xiàn)
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
// 留給子類實(shí)現(xiàn)
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()) { // 重入該鎖 只是累加狀態(tài)
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(); // 屬于運(yùn)行時(shí)異常
boolean free = false;
if (c == 0) { // 判斷該線程是否完全退出
free = true;
setExclusiveOwnerThread(null);
}
setState(c); // 設(shè)置新的狀態(tài)
return free;
}
// 判斷當(dāng)前線程是否持有該鎖
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
// condition相關(guān)博客會(huì)分析
final ConditionObject newCondition() {
return null;
}
// 獲得持有該鎖的線程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
// 獲得持有該鎖的個(gè)數(shù) 其實(shí)就是重入的次數(shù)
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
// 判斷鎖有沒有被占用 true表示被占用 false表示沒有被占用
final boolean isLocked() {
return getState() != 0;
}
// 序列化的部分
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
從該類的實(shí)現(xiàn)可以看到當(dāng)某一個(gè)線程獲得了鎖后可以無限制的重入, 如果達(dá)到了
int變量的最大值后會(huì)拋出Error. 狀態(tài)值為0的時(shí)候表明鎖可以被獲取, 狀態(tài)值為n (n >= 1)的時(shí)候表明該線程重入了n-1次.
非公平鎖NonfairSync的實(shí)現(xiàn)
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 實(shí)現(xiàn)父類的方法
final void lock() {
/**
* 如果獲取鎖成功,則設(shè)置當(dāng)前線程
* 否則去嘗試獲取鎖
*/
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
// 重寫父類的父類AbstractQueuedSynchronizer的tryAcquire方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
其實(shí)沒什么可說的, 需要實(shí)現(xiàn)從父類的
lock()抽象方法和重寫AQS中的tryAcquire方法即可.
公平鎖FairSync的實(shí)現(xiàn)
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)) { // 檢查等待隊(duì)列前面是不是有線程還沒有獲得鎖
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)
lock和tryAcquire方法, 但是有一點(diǎn)不同的是在嘗試獲取鎖的時(shí)候會(huì)先使用hasQueuedPredecessors判斷等待隊(duì)列中該節(jié)點(diǎn)是否有前驅(qū)節(jié)點(diǎn), 如果有前驅(qū)節(jié)點(diǎn)必須要等到前驅(qū)節(jié)點(diǎn)所對應(yīng)的線程獲取并釋放鎖之后才能繼續(xù)獲取鎖。
Lock接口方法的實(shí)現(xiàn)
@Override
public void lock() {
sync.lock();
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
// 該方法不存在公平性的問題,所以直接調(diào)用nonfairTryAcquire方法
@Override
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return null;
}
一些監(jiān)控方法
// 獲取鎖被重入了多少次
public int getHoldCount() {
return sync.getHoldCount();
}
// 判斷鎖是不是被當(dāng)前線程持有
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
// 判斷鎖有沒有被任何一個(gè)線程占有
public boolean isLocked() {
return sync.isLocked();
}
// 判斷該鎖是不是公平鎖
public final boolean isFair() {
return sync instanceof FairSync;
}
// 返回占有鎖的那個(gè)線程
protected Thread getOwner() {
return sync.getOwner();
}
// 返回等待隊(duì)列中是否還有節(jié)點(diǎn)
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
// thread是否在等待隊(duì)列中
public final boolean hasQueuedThread(Thread thread) {
return sync.isQueued(thread);
}
// 返回等待隊(duì)列中的長度
public final int getQueueLength() {
return sync.getQueueLength();
}
// 返回等待隊(duì)列中的所有線程
protected Collection<Thread> getQueuedThreads() {
return sync.getQueuedThreads();
}
注意:
getQueuedThreads()返回等待隊(duì)列中的線程是按倒序的. 具體原因可以參考 [Java源碼][并發(fā)J.U.C]---用代碼一步步實(shí)現(xiàn)AQS(1)---獨(dú)占鎖的獲取和釋放
構(gòu)造方法
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
默認(rèn)是非公平鎖, 可以通過傳入?yún)?shù)來決定使用非公平鎖還是公平鎖.
例子2: 公平鎖和非公平鎖的對比
啟動(dòng)五個(gè)線程去奪取鎖, 每個(gè)線程的作用就是打印當(dāng)前等待隊(duì)列中所有線程的順序, 打印兩次, 在第一次獲取鎖后打印一次后釋放鎖后再去嘗試獲取鎖.
import java.util.concurrent.CountDownLatch;
public class TestFairAndNonFairLock {
static ReentrantLock nonfair = new ReentrantLock(false);
static ReentrantLock fair = new ReentrantLock(true);
static CountDownLatch start = new CountDownLatch(1);
// start 是為了保證5個(gè)線程同時(shí)運(yùn)行 后續(xù)有專門博客會(huì)分析CountDownLatch
public static void main(String[] args) {
test(fair);
// test(nonfair);
}
public static void test (ReentrantLock lock) {
for (int i = 0; i < 5; i ++) {
new Thread(new Runner2(lock), i+"").start();
}
start.countDown();
}
static class Runner2 implements Runnable {
ReentrantLock lock;
public Runner2(ReentrantLock lock) {
this.lock = lock;
}
public void run() {
try {
start.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
lock.lock();
System.out.print("lock by " + Thread.currentThread().getName() + " wait by [");
String str = "";
for (Thread t : lock.getQueuedThreads()) {
str = t.getName() + "," + str;
}
System.out.println(str + "]");
lock.unlock();
lock.lock();
System.out.print("lock by " + Thread.currentThread().getName() + " wait by [");
str = "";
for (Thread t : lock.getQueuedThreads()) {
str = t.getName() + "," + str;
}
System.out.println(str + "]");
lock.unlock();
}
}
}
對比如下:
| 公平鎖 | 非公平鎖 |
|---|---|
| lock by 3 wait by [4,0,1,2,] | lock by 4 wait by [1,3,2,] |
| lock by 4 wait by [0,1,2,3,] | lock by 4 wait by [1,3,2,0,] |
| lock by 0 wait by [1,2,3,4,] | lock by 1 wait by [3,2,0,] |
| lock by 1 wait by [2,3,4,0,] | lock by 1 wait by [3,2,0,] |
| lock by 2 wait by [3,4,0,1,] | lock by 3 wait by [2,0,] |
| lock by 3 wait by [4,0,1,2,] | lock by 3 wait by [2,0,] |
| lock by 4 wait by [0,1,2,] | lock by 2 wait by [0,] |
| lock by 0 wait by [1,2,] | lock by 2 wait by [0,] |
| lock by 1 wait by [2,] | lock by 0 wait by [] |
| lock by 2 wait by [] | lock by 0 wait by [] |
從上圖中可以看到公平鎖在每次釋放鎖后都會(huì)從等待隊(duì)列中的第一個(gè)節(jié)點(diǎn)所對應(yīng)的線程獲得鎖. 而非公平鎖在該線程釋放鎖又基本上再次獲得了鎖, 這是因?yàn)樵卺尫诺倪^程中需要去喚醒等待隊(duì)列中的節(jié)點(diǎn),在喚醒后該線程與喚醒的線程競爭該鎖, 然而從結(jié)果來看剛剛釋放的鎖競爭到鎖的概率比較大.
參考
1. Java并發(fā)編程的藝術(shù)
2. Java1.8 java.util.concurrent.locks包的源代碼
