ReentrantLock源碼分析node
基礎知識複習數據結構
synchronized和lock的區別併發
synchronized是非公平鎖,沒法保證線程按照申請鎖的順序得到鎖,而Lock鎖提供了可選參數,能夠配置成公平鎖,也能夠配置成非公平鎖。一般來講,非公平鎖的效率比公平鎖要高。jvm
一個線程使用syn獲取鎖,除非該線程成功獲取到鎖,不然將一直阻塞住。而Lock鎖提供了lockInterruptibly()接口,提供了可中斷的操做ide
帶超時時間的鎖。Lock鎖提供了tryLock(long time, TimeUnit unit)帶超時時間的獲取鎖的接口,在等待指定時間後,若是獲取不到鎖,則放棄獲取鎖函數
自動釋放鎖。若是用syn加鎖,當發生異常時(比方運行時異常),那麼jvm會自動釋放掉線程持有的鎖,而lock鎖則不會主動釋放,除非調用了unlock接口,所以使用lock鎖時有可能致使死鎖源碼分析
在ReentrantLock上能夠綁定多個Condition條件,也就是能夠擁有多個等待隊列,好比在實現生產者消費者的時候,使用一個隊列(鎖的隊列)存放等待 隊列(生產者消費者的隊列)有元素的消費者,使用另外一個隊列(鎖的隊列)存放等待 隊列not full的生產者,相比較synchronized和wait notify而言,避免了錯誤的喚醒生產者或者消費者的開銷ui
ReentrantLock 基礎分析spa
ReentrantLock是lock的一個實現類,獨佔鎖。首先看一下ReentrantLock的內部屬性線程
private final Sync sync;
1
發如今只有一個Sync類型的屬性,這個Sync是AQS的一個抽象類以下:
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;
}
....
同時在ReentrantLock 內部,實現了Sync2個不一樣的類,一個是NonfairSync(非公平鎖),一個是FairSync(公平鎖)。也就是在一開頭,synchronized和lock的區別的第一點,Lock能夠建立2種不一樣的鎖,根據傳入的參數。
以下源碼
//默認的ReentrantLock無參構造函數,是非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
//當傳入fasle時,就建立了公平鎖。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
AbstractQueuedSynchronizer
那麼Sync的父類是AQS,併發包中的鎖底層就是使用了AQS:AbstractQueuedSynchronizer。因此來看一下AQS,那麼首先來看下屬性:
//這個是父類AbstractOwnableSynchronizer中的屬性,標識了獨佔模式下獲取鎖的是哪個線程
private transient Thread exclusiveOwnerThread;
//AQS的數據結構是FIFO的雙向隊列
//頭部節點
private transient volatile Node head;
//尾部節點
private transient volatile Node tail;
//同步狀態值
private volatile int state;
//設置這裏使用CAS來更新 主要用於對state的更新
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;
能夠看到有頭部節點和尾部節點外,還有一個同步狀態值state,該屬性在不一樣的鎖中,表明了不一樣的含義。
在ReentrantLock中,state能夠用來表示當前線程獲取鎖的可重入次數
對與ReentrantReadWriteLock來講,高16位表明了讀鎖,也就是讀鎖的次數,低16位表明了寫鎖的可重入次數
在Semaphore中,state表明了可用信號的個數
在countdownlatch中,state用來表示計數器當前的值
因此在ReentrantLock 中 state用來表示當前線程獲取鎖的可衝入次數。當沒有線程持有鎖的時候,state爲0,當有一個線程獲取鎖時,state爲1。而後當前線程須要再次獲取鎖時,發現本身已是鎖的持有者,state+1
看下AQS的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;
在已知隊列中的節點是這樣的結構狀況下,來看下何如進行獲取資源
//根據源碼中的英文來翻譯,就是獲取獨佔資源時調用該方法,
//也就是使用tryAcquire來改變state的值,若是失敗就會把線程封裝成Node.EXCLUSIVE放入隊列尾部並掛起
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//其中 tryAcquire是在實現類中具體實現的
對應的釋放資源
//tryRelease是在實現類中具體實現的方法,若是成功釋放資源,則激活隊列中的一個線程,也就是頭部節點的線程。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
以上2個release和acquire,的最多見的實現例子就是Lock中的unlock和lock。可是在AQS源碼中還存在着一個相似的方法,acquireInterruptibly ,這個方法和acquire功能同樣,可是帶Interruptibly 的方法,就是能夠對中斷進行響應。若是該線程在等待過程當中被中斷了,那麼帶Interruptibly 的方法就會拋出異常,也會終端。不然不會。對應了一開始的第二條,lockInterruptibly()能夠對中斷進行響應。
再來看一下在AQS中,是怎麼進行加入隊列操做的。
//有2種方式,若是尾部節點不爲空,就直接加入封裝好的node,爲空則調用enq()加入隊列
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
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
//加入一個空的node爲哨兵節點
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
也就是說,當隊列爲空時,在加入第一個節點的時候,會先加入一個哨兵節點。
ReentrantLock 中的lock
這裏分爲2中狀況分析lock()方法,以前說過ReentrantLock中的Sync有非公平和公平2中模式。又結合AQS的源碼分析,因此咱們知道區別在於FairSync 和NonfairSync 2個類中的 tryAcquire 實現不一樣。
FairSync 公平狀況以下:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//當state爲0時,就標識空閒,能夠被獲取
if (c == 0) {
//hasQueuedPredecessors是公平策略,
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;
}
能夠看到對state的修改都是運行了CAS來更新,同時,在修改state的基礎上,使用了公平策略hasQueuedPredecessors是在AQS中的方法
//判斷當前節點是不是隊列的第一個節點,若是當前節點有前驅結點返回true,當前隊列爲空或者當前節點是第一個節點則返回false
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
這裏最後的判斷能夠分開來看,h==s則是當前隊列爲空,直接返回false,若是h!=s且s==null則是,說明有一個元素將要做爲AQS的第一個節點入隊列,那麼返回true,若是,s.thread != Thread.currentThread()) 就表明,第一個元素就不是當前的線程,返回true。
NonfairSync 非公平狀況以下鄭州不孕不育醫院:http://www.zzfkyy120.com/
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//非公平獲取
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//缺乏了校驗公平的策略,直接對state進行修改
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;
}
因此按照以上Lock的邏輯,lockInterruptibly即是調用了acquireInterruptibly的方法來獲取資源
public final void acquireInterruptibly(int arg)
throws InterruptedException {
//判斷線程的狀態
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
ReentrantLock 中的unlock
ReentrantLock 中的unlock,是沒有策略之分,在Snyc中就實現了方法
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;
}
unlock的代碼就比較好讀了,先判斷lock的持有線程與當前線程是否一致,而後是的話,就把state和持有線程清空。最後在AQS中,會移除該節點
固然在AQS中,還存在一個條件隊列,在後續文章再談。
下面是ReentrantLock 的一個例子,模擬領取優惠卷的狀況
public class ReenTrantLockDemo extends Thread{
//模擬優惠卷
private static List array = new ArrayList<>();
private static Lock lock = new ReentrantLock();
public Integer get(){
lock.lock();
try {
Integer o= array.get(0);
array.remove(o);
return o;
}catch (Exception e){
System.out.println("獲取出錯");
}finally {
lock.unlock();
}
return -1;
}
@Override
public void run() {
Integer a = get();
System.out.println("獲取到的優惠卷編號爲"+a);
}
public static void main(String[] args) {
for(int i=0;i<10;i++){
array.add(i);
}
ReenTrantLockDemo reenTrantLockDemo = new ReenTrantLockDemo();
ExecutorService service = Executors.newCachedThreadPool();
ReenTrantLockDemo demo = new ReenTrantLockDemo();
for (int i = 0; i < 10; i++) {
service.submit(demo);
}
service.shutdown();
}
}
結果爲
獲取到的優惠卷編號爲3
獲取到的優惠卷編號爲2
獲取到的優惠卷編號爲6
獲取到的優惠卷編號爲5
獲取到的優惠卷編號爲1
獲取到的優惠卷編號爲0
獲取到的優惠卷編號爲4
獲取到的優惠卷編號爲8
獲取到的優惠卷編號爲7
獲取到的優惠卷編號爲9
10
去掉lock的結果爲:
獲取到的優惠卷編號爲0
獲取到的優惠卷編號爲5
獲取到的優惠卷編號爲0
獲取到的優惠卷編號爲0
獲取到的優惠卷編號爲0
獲取到的優惠卷編號爲7
獲取到的優惠卷編號爲4
獲取到的優惠卷編號爲8
獲取到的優惠卷編號爲0
獲取到的優惠卷編號爲6
名詞解釋
公平鎖與非公平鎖 :是否按照線程進入阻塞隊列的順序來執行