ReentrantLock詳解

hahaeee發表於2018-04-26

RenntrantLock

依賴關係

斜體為抽象類,下橫線為介面

聚合關係總結:

  1. ReentrantLock實現了Lock,Serializable介面
  2. ReentrantLock.Sync(內部類)繼承了AQS
  3. ReentrantLock.NonfairSync和ReentrantLock.FairSync繼承了ReentrantLock.Sync
  4. ReentrantLock持有ReentrantLock.Sync物件(實現鎖功能)

鎖實現總結:

  1. 由Node節點組成一條同步佇列(有head,tail兩個指標,並且head初始化時指向空節點)
  2. int state標記鎖使用數量(獨佔鎖時,通常為1,發生重入時>1)
  3. lock()時加到佇列尾部
  4. unlock()時,釋放head節點,並指向下一個節點head=head.next,然後喚醒當前head節點

性質:

  1. 獨佔鎖(排它鎖):只能有一個執行緒獲取鎖
  2. 重入鎖:一個執行緒可以多次lock()
  3. 公平/非公平鎖:只針對上鎖過程
    1. 非公平鎖:嘗試獲取鎖,若成功立刻返回,失敗則加入同步佇列
    2. 公平鎖:直接加入同步佇列

Lock

Lock介面定義了鎖的行為

public interface Lock {
	//上鎖(不響應Thread.interrupt()直到獲取鎖)
    void lock();
	//上鎖(響應Thread.interrupt())
    void lockInterruptibly() throws InterruptedException;
	//嘗試獲取鎖(以nonFair方式獲取鎖)
    boolean tryLock();
  	//在指定時間內嘗試獲取鎖(響應Thread.interrupt(),支援公平/二階段非公平)
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
	//解鎖
    void unlock();
	//獲取Condition
    Condition newCondition();
}

複製程式碼

lock()過程

//鎖具體實現
private final Sync sync;
//根據傳入引數選擇FairSync或NonfairSync實現
public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() {
	sync.lock();
}
#java.util.concurrent.locks.ReentrantLock.Sync
abstract void lock();
複製程式碼

公平鎖

加入同步佇列(當同步佇列為空時會直接獲得鎖),等待鎖

#java.util.concurrent.locks.ReentrantLock.FairSync
final void lock() {
	acquire(1);
}
#java.util.concurrent.locks.AbstractQueuedSynchronizer
public final void acquire(int arg) {
	if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		selfInterrupt();
}
複製程式碼

acquire()流程:

  1. tryAcquire():模板方法,獲取鎖

     #java.util.concurrent.locks.ReentrantLock.FairSync
     protected final boolean tryAcquire(int acquires) {
     	//獲取當前執行緒
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {//當前鎖沒被佔用
     	   if (!hasQueuedPredecessors() &&//1.判斷同步佇列中是否有節點在等待
     		   compareAndSetState(0, acquires)) {//2.如果上面!1成立,修改state值(表明當前鎖已被佔用)
     		   setExclusiveOwnerThread(current);//3.如果2成立,修改當前佔用鎖的執行緒為當前執行緒
     		   return true;
     	   }
        }
        else if (current == getExclusiveOwnerThread()) {//佔用鎖執行緒==當前執行緒(重入)
     	   int nextc = c + acquires;//
     	   if (nextc < 0)
     		   throw new Error("Maximum lock count exceeded");
     	   setState(nextc);//修改status
     	   return true;
        }
        return false;//直接獲取鎖失敗
    }
    複製程式碼
  2. acquireQueued(addWaiter(Node.EXCLUSIVE), arg):加入同步佇列

    #java.util.concurrent.locks.AbstractQueuedSynchronizer
    //1
    private Node addWaiter(Node mode) {
     //生成node
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        if (pred != null) {
     	//將node加到佇列尾部
     	   node.prev = pred;
     	   if (compareAndSetTail(pred, node)) {
     		   pred.next = node;
     		   return node;
     	   }
        }
        //如果加入失敗(多執行緒競爭或者tail指標為null)
        enq(node);
        return node;
    }
    //1.1  
    private Node enq(final Node node) {
     //死迴圈加入節點(cas會失敗)
        for (;;) {
     	   Node t = tail;
     	   if (t == null) { //tail為null,同步佇列初始化
     		//設定head指標
     		   if (compareAndSetHead(new Node()))//注意這裡是個空節點!!
     			   tail = head;//將tail也指向head
     	   } else {
     		   node.prev = t;//將當前node加到隊尾
     		   if (compareAndSetTail(t, node)) {
     			   t.next = node;
     			   return t;//注意這裡才返回
     		   }
     	   }
        }
    }
    //2
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
     	//表示是否被打斷
     	   boolean interrupted = false;
     	   for (;;) {
     		//獲取node.pre節點
     		   final Node p = node.predecessor();
     		   if (p == head //當前節點是否是同步佇列中的第二個節點
     		   && tryAcquire(arg)) {//獲取鎖,head指向當前節點
     			   setHead(node);//head=head.next
     			   p.next = null;//置空 
     			   failed = false;
     			   return interrupted;
     		   }
    
     		   if (shouldParkAfterFailedAcquire(p, node) && //是否空轉(因為空轉喚醒是個耗時操作,進入空轉前判斷pre節點狀態.如果pre節點即將釋放鎖,則不進入空轉)
     			   parkAndCheckInterrupt())//利用unsafe.park()進行空轉(阻塞)
     			   interrupted = true;//如果Thread.interrupt()被呼叫,(不會真的被打斷,會繼續迴圈空轉直到獲取到鎖)
     	   }
        } finally {
     	   if (failed)//tryAcquire()過程出現異常導致獲取鎖失敗,則移除當前節點
     		   cancelAcquire(node);
        }
    }
    複製程式碼

    過程總結:

    1. 空轉(如果當前節點是同步佇列中的第二個節點,則直接獲得鎖返回)
    2. 獲得鎖

    注意:這裡有兩次tryAcquire()過程.第一次,為了避免同步佇列為空時還插入佇列產生的效能耗費(cas空轉).第二次,就是正常的流程.先插入隊尾,然後等待喚醒,再獲取鎖

  3. selfInterrupt(): 喚醒當前執行緒

    static void selfInterrupt() {//在獲取鎖之後 響應intterpt()請求
    	Thread.currentThread().interrupt();
    }
    複製程式碼

非公平鎖

一階段

#java.util.concurrent.locks.ReentrantLock.NonfairSync
final void lock() {
	//在acquire()之前先嚐試獲取鎖
	if (compareAndSetState(0, 1))
		setExclusiveOwnerThread(Thread.currentThread());
	else
		acquire(1);
}
複製程式碼

二階段 acquire()流程與公平鎖一模一樣,唯一區別在於tryAcquire()實現中

#java.util.concurrent.locks.ReentrantLock.NonfairSync
protected final boolean tryAcquire(int acquires) {
 	return nonfairTryAcquire(acquires);
 }
 
#java.util.concurrent.locks.ReentrantLock.Sync
 final boolean nonfairTryAcquire(int acquires) {//這個過程其實和FairSync.tryAcquire()基本一致
	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;
}
複製程式碼
區別點 lock()過程(一階段) tryAcquire()過程(二階段)
FairSync 直接acquire() 當前若無執行緒持有鎖,如果同步佇列為空,獲取鎖
NonFairSync 先嚐試獲取鎖,再acquire() 當前若無執行緒持有鎖,獲取鎖

unlock()過程

#java.util.concurrent.locks.ReentrantLock
public void unlock() {
	sync.release(1);
}
#java.util.concurrent.locks.AbstractQueuedSynchronizer
public final boolean release(int arg) {
if (tryRelease(arg)) {//釋放鎖
	Node h = head;
	if (h != null &&//head節點為空(非公平鎖直接獲取鎖)
	h.waitStatus != 0)
		unparkSuccessor(h);//喚醒同步佇列中離head最近的一個waitStatus<=0的節點
	return true;
}
return false;
}
#java.util.concurrent.locks.ReentrantLock
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);
	}
	//state==0(此時持有鎖,不用cas)
	setState(c);
	return free;
}
複製程式碼

lockInterruptibly()過程

lockInterruptibly()與lock()過程基本相同,區別在於Thread.intterpt()的應對措施不同

//lock()
final boolean acquireQueued(final Node node, int arg) {
	boolean failed = true;
	try {
		//表示是否被打斷
		boolean interrupted = false;
		for (;;) {
			//獲取node.pre節點
			final Node p = node.predecessor();
			if (p == head //當前節點是否是同步佇列中的第二個節點
			&& tryAcquire(arg)) {//獲取鎖,當前head指向當前節點
				setHead(node);//head=head.next
				p.next = null;//置空 
				failed = false;
				return interrupted;
			}

			if (shouldParkAfterFailedAcquire(p, node) && //是否空轉(因為空轉喚醒是個耗時操作,進入空轉前判斷pre節點狀態.如果pre節點即將釋放鎖,則不進入空轉)
				parkAndCheckInterrupt())//利用unsafe.park()進行空轉(阻塞)
				interrupted = true;//如果Thread.interrupt()被呼叫,(不會真的被打斷,會繼續迴圈空轉直到獲取到鎖)
		}
	} finally {
		if (failed)//tryAcquire()過程出現異常導致獲取鎖失敗,則移除當前節點
			cancelAcquire(node);
	}
}
// lockInterruptibly()
private void doAcquireInterruptibly(int arg)
	throws InterruptedException {
	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; 
				failed = false;
				return;
			}
			if (shouldParkAfterFailedAcquire(p, node) &&
				parkAndCheckInterrupt())//唯一區別當Thread.intterpt()打斷時,直接丟擲異常
				throw new InterruptedException();
		}
	} finally {
		if (failed)//然後移除當前節點
			cancelAcquire(node);
	}
}
複製程式碼

tryLock()

#java.util.concurrent.locks.ReentrantLock
public boolean tryLock() {
	//嘗試獲取非公平鎖
	return sync.nonfairTryAcquire(1);
}
複製程式碼

tryLock(long timeout, TimeUnit unit)

#java.util.concurrent.locks.ReentrantLock
public boolean tryLock(long timeout, TimeUnit unit)
		throws InterruptedException {
	return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
#java.util.concurrent.locks.AbstractQueuedSynchronizer
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 {
	...
	final long deadline = System.nanoTime() + nanosTimeout;
	//加入隊尾
	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; 
				failed = false;
				return true;
			}
		  //上面與acquireQueued()相同,重點看這裡
		  //計算剩餘時間
			nanosTimeout = deadline - System.nanoTime();
			if (nanosTimeout <= 0L)
				return false;
			if (shouldParkAfterFailedAcquire(p, node) &&
				nanosTimeout > spinForTimeoutThreshold)
				//利用parkNanos()指定空轉時間
				LockSupport.parkNanos(this, nanosTimeout);
			if (Thread.interrupted())//如果被Thread.interrupt(),則拋異常
				throw new InterruptedException();
		}
	} finally {
		if (failed)//移除節點
			cancelAcquire(node);
	}
}
複製程式碼

newCondition()

public Condition newCondition() {
	return sync.newCondition();
}
#java.util.concurrent.locks.ReentrantLock.Sync
final ConditionObject newCondition() {
	return new ConditionObject();
}
複製程式碼

相關文章