1.什麼是鎖?
鎖是用來控制多個線程訪問共享資源的方式,一般來說,鎖是用來防止多個線程同時獲取共享資源。在java 1.5 之前,使用synchronized 關鍵字來實現鎖的功能,1.5之後提供了lock 接口,雖然lock接口失去了synchronized 隱式獲取和釋放鎖的便捷,但是
卻提供了獲取和釋放鎖的可操作性,可中斷的獲取鎖以及超時獲取鎖等 synchronized 等不具備的同步特性
2.使用示例
Lock lock = new ReentrantLock();
lock.lock();
try{
.......
}finally{
lock.unlock();
}
注意:1.synchronized同步塊執行完成或者遇到異常是鎖會自動釋放 而 lock必須調用unlock 方法釋放鎖,因此在finally塊中釋放鎖。
3.Lock 接口
public interface Lock {
//獲取鎖,如果鎖不可用,當前線程被禁用以進行線程調度,在獲取到鎖之前,線程處於休眠狀態
void lock();
//獲取鎖的過程能夠響應線程中斷,線程是可以被自己中斷的 interupt 方法
void lockInterruptibly() throws InterruptedException;
//獲取鎖時鎖是可用的立即返回true 否則立即返回false
boolean tryLock();
//如果線程在等待的時間內是空閒的並且未被中斷則獲取鎖
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//釋放鎖
void unlock();
////獲取與lock綁定的等待通知組件,當前線程必須持有鎖才能進行等待,在開始等待之前會釋放鎖,在等待返回之前或重新獲取鎖
Condition newCondition();
}
ReentrantLock 繼承了lock
public class ReentrantLock implements Lock, java.io.Serializable {
//獲取鎖,如果鎖不可用,當前線程被禁用以進行線程調度,在獲取到鎖之前,線程處於休眠狀態
void lock(){
sync.lock();
}
//獲取鎖的過程能夠響應線程中斷,線程是可以被自己中斷的 interupt 方法
void lockInterruptibly() throws InterruptedException{
sync.acquireInterruptibly(1);
}
//獲取鎖時鎖是可用的立即返回true 否則立即返回false
boolean tryLock(){
return sync.nonfairTryAcquire(1);
}
//如果線程在等待的時間內是空閒的並且未被中斷則獲取鎖
boolean tryLock(long time, TimeUnit unit) throws InterruptedException{
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
//釋放鎖
void unlock(){
sync.release(1);
}
////獲取與lock綁定的等待通知組件,當前線程必須持有鎖才能進行等待,在開始等待之前會釋放鎖,在等待返回之前或重新獲取鎖
Condition newCondition(){
return sync.newCondition();
}
}
可以看出ReentrantLock 實際是面向使用者的一個封裝類,lock真正的實現是由sync 來實現的,通過源碼發現這些並不是sync自己實現的而是它的父類 AbstractQueuedSynchronizer(隊列同步器AQS)實現的.
AQS 數據結構
struct Node{
struct Node * pre;//前驅節點
struct Node * pNext;//後繼節點
int waitStatus;
Thread * thread;//線程
struct Node * nextWaiter;// 模式 Node.EXCLUSIVE 獨佔, Node.SHARED 共享
}
隊列
struct stack{
struct Node * pHead;
struct Node * pTail;
int state //當前同步狀態
}
//雙鏈表實現的隊列
//1.初始化和2.入隊 同時實現的
private Node enq(final Node node) {
//自旋
for (;;) {
Node t = tail;
//尾節點爲空則創建一個節點設置爲頭節,並將尾節點指向頭節點
//頭節點、尾節點只是爲了方便操作
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
//1.將插入的節點的前驅節點指向尾節點 2.將插入的節點設置爲新的尾節點3.將之前的尾節點後繼節點指向 插入的節點
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
//3.出隊
1.直接從lock.lock() 入手
/**
* Sync object for non-fair locks
*/
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() {
//如果state 的值是0,初始化爲0,直接將當前線程設置爲獨有訪問權限的線程,將狀態設置爲1
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else//否則執行acquire
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
這裏要引入 Unsafe.objectFieldOffset(Field),註釋說是爲了CAS 操作必須初始化一些參數,這應該是屬性的內存編號偏移量
static {
try {
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
2.acquire()
public final void acquire(int arg) {
//如果不能將當前線程設置爲獨佔隊列同步器訪問權限的線程,即未獲取到鎖
// 執行addWaiter 並執行acquireQueued 方法
// acquireQueued 會一直阻塞直到產生的新節點的前驅節點是頭節點並且狀態爲signal
//如果在阻塞過程中當前線程出現過中斷 那麼會重新產生一箇中斷
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire() 的實現調用
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//獲取當前同步狀態
int c = getState();
if (c == 0) {
//如果當前狀態是0 直接將狀態更新爲1 並將當前線程設置爲同步器獨佔訪問權限的線程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//如果當前線程獨佔訪問權限的線程,並且狀態不爲0
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//將狀態設置爲c + acquires(acquires = 1)
setState(nextc);
return true;
}
//否則返回false
return false;
}
//三種情況:1. state == 0,直接將當前線程設置爲獨佔訪問權限的線程,將狀態改爲acquires,從
//lock.lock() 那裏走過來,acquires的值爲1->state =1,返回true;
//2.state != 0 並且 當前線程就是獨佔訪問權限的線程,如果state+1 <0 拋異常,否則將state設
//置爲state+1,返回true
//3. 其他情況返回false
addWaiter(Node.EXCLUSIVE){
//將thread 封裝成一個 node
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;
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {//自旋
//當前節點的前驅節點
final Node p = node.predecessor();
//如果當前節點的前驅節點是頭節點將嘗試獲取鎖
//1.如果獲取到鎖 將當前節點設置爲頭節點
//斷開之前頭節點指向當前節點的指針,方便內存回收,返回false
//相當於出隊
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//2.其他任何情況,在node 的前驅成爲頭節點之前會無線循環檢查線程是否中斷
//如果已經中斷第二次檢查的時候會返回false
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
//如果線程應該阻塞返回true 否則false
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//pred 是node 的前驅節點
int ws = pred.waitStatus;
//如果前驅節點的狀態已經是Node.SIGNAL 返回true
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
//
if (ws > 0) {//大於1 只有一種情況 cancelled
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
//如果前驅節點被取消,將node 的前驅節點指向 pred 的前一個節點,但是pred 的後一個節點仍舊指向node,這樣會導致最多2個節點的後繼指向node
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
//將pred 的後繼節點指向node
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
//將前驅節點的狀態設置爲SIGNAL 等待
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
//禁用當前線程進行線程調度,並讓當前線程處於休眠狀態直到被喚醒(unpark,interupt)
LockSupport.park(this);
return Thread.interrupted();//如果當前線程是中斷狀態調用之後會清除狀態,第二次返回false
}
//線程的中斷只是將中斷標誌位設爲true 當調用類似wait sleep 方法的時候會拋出異常並重置
//中斷爲false 喚醒線程,
//當node 的前驅成爲頭節點的時候,中斷當前線程
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
//獨佔模式的釋放-> 釋放頭節點,直到隊列同步器free 纔算完全釋放
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
//頭節點不爲空且頭節點的狀態不爲初始化
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果state == 0 將隊列同步器設置爲空閒狀態,將獨佔訪問權限的線程置爲空
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//將狀態設置爲 state - 1
setState(c);
return free;
}
總結:每次調用lock.lock 的時候都會將state +1,釋放的時候減一
//node 爲頭節點
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
//不爲cancelled 將waitStatus 設置爲0 初始化狀態
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
//頭節點的後繼節點
Node s = node.next;
//找到沒有cancel 的後繼節點
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);
}
總結:
獨佔式鎖按照隊列的排列順序獲取鎖
1.調用lock.lock() 的時候,先檢查隊列是否有初始化,沒初始化先初始化隊列的頭節點,然後將當前線程封裝成一個node(節點)插入在隊列末尾,如果已經初始化,並且當前線程就是持有鎖的線程,將state 設置成state+1。其他情況會嘗試獲取鎖(state == 0 或者當前線程是持有鎖的線程纔會獲取到鎖)將當前線程封裝成node(節點) 插入隊列末尾,並且讓當前線程休眠,將node 的狀態waitstatus設置成singal。如果獲取到鎖會將頭節點出隊,並將當前節點設置爲頭節點。
2.當前持有鎖的節點(頭節點)的線程不會休眠,當你手動unlock 的時候,會釋放鎖,此時會unpark 喚醒下個節點的線程
總體來說:在獲取同步狀態時,AQS維護一個同步隊列,獲取同步狀態失敗的線程會加入到隊列中進行自旋;移除隊列(或停止自旋)的條件是前驅節點是頭結點並且成功獲得了同步狀態。在釋放同步狀態時,同步器會調用unparkSuccessor()方法喚醒後繼節點。
可中斷獲取鎖lock.lockInterruptibly()
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))//沒有獲取到鎖
doAcquireInterruptibly(arg);
}
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
//將當前線程封裝成node 加入到隊列末尾
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
//自旋
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
//出隊
p.next = null; // help GC
failed = false;
return;
}
//將p 的waitstatus設置爲singal 讓當前線程休眠
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//當線程中斷的時候拋出異常
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
超時等待式獲取鎖(tryAcquireNanos()方法)
通過調用lock.tryLock(timeout,TimeUnit)方式達到超時等待獲取鎖的效果,該方法會在三種情況下才會返回:
- 在超時時間內,當前線程成功獲取了鎖;
- 當前線程在超時時間內被中斷;
- 超時時間結束,仍未獲得鎖返回false。
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); } private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (nanosTimeout <= 0L) return false; //計算超時時間 final long deadline = System.nanoTime() + nanosTimeout; //將當前線程封裝成node 入隊 final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { //獲取到鎖就讓頭節點出隊,把自己設成頭節點 setHead(node); p.next = null; // help GC failed = false; return true; } //計算超時時間間隔 nanosTimeout = deadline - System.nanoTime(); if (nanosTimeout <= 0L)//表示已經超時 return false; if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) //這段時間間隔之內當前線程不可調度,休眠 LockSupport.parkNanos(this, nanosTimeout); if (Thread.interrupted()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
疑問1.LockSupport.park 到底是幹什麼的?文檔上說以下三種情況會返回,否則會調用pthread.wait 進入等待狀態
1.Some other thread invokes unpark with the current thread as the target;//調用了unparkSome
2.other thread interrupts the current thread; or// 調用了線程的interrupt
3.The call spuriously (that is, for no reason) returns.//莫名其妙的返回
所以那個自旋在線程進入wait 狀態之後並沒有繼續執行,直到unpark 或者interrupt,當線程在wait狀態被中斷的時候 線程會被喚醒進入自旋並拋出異常,所以如果線程中斷都是立即拋出異常的。
疑問2.這些沒獲取到同步狀態的節點是怎麼出隊的?
那些返回false 將 cancelAcquire 出隊
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;
// Skip cancelled predecessors
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {
pred.compareAndSetNext(predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && pred.compareAndSetWaitStatus(ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
pred.compareAndSetNext(predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
共享鎖
public final void acquireShared(int arg) {
//tryAcquireShared 沒找到有實現這個方法的類
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
//加入隊列
final Node node = addWaiter(Node.SHARED);
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
//獲取到鎖將頭節點出隊,讓當前節點成爲頭節點
setHeadAndPropagate(node, r);
p.next = null; // help GC
//如果線程中斷過再次調用interrupt
if (interrupted)
selfInterrupt();
return;
}
}
//將頭節點的waitstatus 設置爲signal 讓當前線程進入wait
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!h.compareAndSetWaitStatus(0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
//喚醒下個節點的線程
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
node.compareAndSetWaitStatus(ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node p = tail; p != node && p != null; p = p.prev)
if (p.waitStatus <= 0)
s = p;
}
if (s != null)
LockSupport.unpark(s.thread);
}
沒找到共享鎖的實現,但方式差不多,獲取到鎖將當前節點設置爲頭節點,前任頭節點出隊
總結:將java源碼以數據結構的方式打開,隊列同步器 不過是一個隊列,指定了入隊出隊的方式,頭節點能獲取到鎖,其他節點無法獲取到鎖,其他節點關聯的線程進入等待狀態,直到頭節點釋放鎖出隊,新的頭節點獲取到鎖並喚醒線程。無法獲取到的鎖的節點會cancelAquire 出隊