Lock作爲多線程編程中的一個基礎,與Lock對應的關鍵字爲synchronized,同時大家都知道lock與synchronized的一個明顯區別是Lock中的鎖可以是公平鎖,synchronized只能爲非公平鎖。本文將通過分析jdk的源碼來了解ReentrantLock中公平鎖和非公平鎖的實現原理,這本是一個老生常談的話題,因爲想了解其過程的話比較簡單的,網上一搜出來也是一大把,但網上的其它文章對於公平鎖和非公平鎖的區別很少有看到有實際實例,基於這個點我便決定自己來寫個實例測試下公平/非公平鎖的不同點,通過實例來了解與再次學習下jdk1.8中ReentrantLock關於非公平鎖和公平鎖的實現過程是怎麼樣的。
先來看一個Lock中公平鎖與非公平鎖的代碼:
ReentrantLock非公平鎖示例代碼
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class NonFairLockTestMain {
public static void main(String[] args) {
Lock nonFairLock = new ReentrantLock(false);
Thread firstThread = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().toString() + " pre run...");
nonFairLock.lock();
Thread.sleep(1_000);
System.out.println(Thread.currentThread().toString() + " run!!!!!!!!!!!!!!!!!!");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().toString() + " pre unlock@@@");
nonFairLock.unlock();
}
}
}, "firstThread");
//第一個線程先獲取到鎖
firstThread.start();
List<Thread> threadList = new ArrayList<>();
//定義101個線線,標號都爲2000到2100之間
for (int i = 2000; i < 2101; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().toString() + " pre run...");
nonFairLock.lock();
System.out.println(Thread.currentThread().toString() + " run!!!");
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
nonFairLock.unlock();
}
}
}, "thread_" + i);
threadList.add(thread);
}
//讓N個線程去排隊獲取鎖,標號都爲100以下
for (int i = 0; i < 101; i++) {
int finalI = i;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().toString() + " pre run...");
nonFairLock.lock();
System.out.println(Thread.currentThread().toString() + " run!!!");
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
nonFairLock.unlock();
if (finalI == 0) {
threadList.forEach(thread1 -> {
thread1.start();
});
}
}
}
}, "thread_" + i);
thread.start();
}
}
}
運行幾次之後,觀察輸出結果,發現會出現如下這種情況:
其輸出片段如下:
Thread[thread_98,5,main] pre run...
Thread[thread_99,5,main] pre run...
Thread[thread_100,5,main] pre run...
Thread[firstThread,5,main] run!!!!!!!!!!!!!!!!!!
Thread[firstThread,5,main] pre unlock@@@
Thread[thread_1,5,main] run!!!
Thread[thread_0,5,main] run!!!
Thread[thread_2,5,main] run!!!
Thread[thread_3,5,main] run!!!
Thread[thread_4,5,main] run!!!
Thread[thread_2000,5,main] pre run...
Thread[thread_2003,5,main] pre run...
Thread[thread_2002,5,main] pre run...
Thread[thread_2001,5,main] pre run...
Thread[thread_2004,5,main] pre run...
Thread[thread_2006,5,main] pre run...
Thread[thread_2010,5,main] pre run...
Thread[thread_2007,5,main] pre run...
Thread[thread_2008,5,main] pre run...
Thread[thread_2009,5,main] pre run...
Thread[thread_2005,5,main] pre run...
Thread[thread_2011,5,main] pre run...
Thread[thread_2012,5,main] pre run...
Thread[thread_2013,5,main] pre run...
Thread[thread_2014,5,main] pre run...
Thread[thread_2015,5,main] pre run...
Thread[thread_2016,5,main] pre run...
Thread[thread_2017,5,main] pre run...
Thread[thread_2018,5,main] pre run...
Thread[thread_2019,5,main] pre run...
Thread[thread_2020,5,main] pre run...
Thread[thread_2021,5,main] pre run...
Thread[thread_2022,5,main] pre run...
Thread[thread_2023,5,main] pre run...
Thread[thread_2024,5,main] pre run...
Thread[thread_2025,5,main] pre run...
Thread[thread_2026,5,main] pre run...
Thread[thread_2027,5,main] pre run...
Thread[thread_2027,5,main] run!!!
Thread[thread_2028,5,main] pre run...
批號爲0~100的線程還未運行完畢時,編號爲2027的這個線程就先運行了!
這是啥?編號爲2027號的線程插隊了!
非公平鎖原理分析
進入lock的lock()方法查看:
/**
* Acquires the lock.
*
* <p>Acquires the lock if it is not held by another thread and returns
* immediately, setting the lock hold count to one.
*
* <p>If the current thread already holds the lock then the hold
* count is incremented by one and the method returns immediately.
*
* <p>If the lock is held by another thread then the
* current thread becomes disabled for thread scheduling
* purposes and lies dormant until the lock has been acquired,
* at which time the lock hold count is set to one.
*/
public void lock() {
sync.lock();
}
在非公平鎖中,其sync.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() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
NonfairSync鎖當前空閒狀態時處理辦法
從代碼裏可以看出,lock方法執行的時候會先用cas來判斷當前鎖是否有線程在佔用,如果cas成功,也就是成功將1設到state上去了的話,那麼當時鎖是沒有線程在佔用的,那麼最後會執行將當前線程設到AbstractOwnableSynchronizer中
/**
* Sets the thread that currently owns exclusive access.
* A {@code null} argument indicates that no thread owns access.
* This method does not otherwise impose any synchronization or
* {@code volatile} field accesses.
* @param thread the owner thread
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
AbstractOwnableSynchronizer是個啥?看下非公平鎖NonfairSync的繼承關係:
Nonfair間接繼承於AbstractOwnableSynchronizer,也就是把當前線程設爲此鎖的擁有者
NonfairSync鎖當前處於非空閒狀態時處理辦法
還是看上方的代碼,當cas(0,1)失敗後代表當前鎖裏目前有線程在使用了,那麼會執行acquire(1),其源碼爲:
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire方法會執行如下幾個判斷:
- !tryAcquire(1)
- acquireQueued(addWaiter(Node.EXCLUSIVE),1))
tryAcquire方法
當前環境下的tryAcquire(1)代碼爲:
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
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;
}
也就是再次判斷state是否真的沒有別的線程在佔用(返回true),以及是否當前線程早就已經佔用了此鎖了(返回true),否則返回false
然後再瞭解下acquireQueued(addWaiter(Node.EXCLUSIVE),1))
addWaiter方法
先看addWaiter(Node.EXCLUSIVE)方法(java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter):
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
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;
}
上面的代碼也就是排隊,排在AbstractQueuedSynchronizer裏,也就是人人常說的AQS裏。
enq(node):
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq方法採用了自旋非阻塞的方式進行了入隊操作
acquireQueued方法
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//獲取當前節點的前驅節點
final Node p = node.predecessor();
//當前節點爲第二個節點時,嘗試獲取鎖
if (p == head && tryAcquire(arg)) {
//獲取鎖成功後,將當前節點設爲頭節點
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//判斷是否可以掛起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
此方法的for循環中主要有兩個if判斷,先看一下第二個if判斷,其中有兩個方法
分別是shouldParkAfterFailedAcquire(p, node)和parkAndCheckInterrupt()
再具體看下
shouldParkAfterFailedAcquire(p, node)方法
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//前驅節點的狀態
int ws = pred.waitStatus;
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) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
// CANCELLED = 1;
// SIGNAL = -1;
// CONDITION = -2;
// PROPAGATE = -3;
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.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
此方法爲判斷此當前線程能否可以掛起的方法,從代碼可以看出,線程能否被掛起的條件是:
當前線程節點的前置節點的waitStatus狀態爲SIGNAL
parkAndCheckInterrupt方法
- !tryAcquire(1) //再次確定當前鎖非空閒
- acquireQueued(addWaiter(Node.EXCLUSIVE),1))
如果上面的這兩個判斷都ok的話,最終會執行一個selfInterrupt(),也就是自己中斷自己
ReentrantLock公平鎖示例代碼
寫着寫着就感覺有點寫偏題了,說好的只來記錄下公平鎖和非公平鎖的實現部分的,結果還記錄了一些關於ReentrantLock相關的代碼,尷尬了。。。
爲了節約文章篇幅,這裏就不再將公平鎖演示的代碼貼出來了,代碼與非公平鎖的示例代碼一樣,只需要將在創建鎖時將鎖new爲ReentrantLock(true)就可以了,即:new ReentrantLock(true)
然可嘗試運行幾次,觀察輸出結果,從結果中會發現就不會再出現插隊的情況了
公平鎖原理分析
公平鎖中調用鎖時調用的是FairSync的lock方法,其lock方法會通過acquire(1)方法調用FairSync下的tryAcquire方法
java.util.concurrent.locks.ReentrantLock.FairSync
/**
* Sync object for fair locks
*/
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;
}
}
再來回顧對比下非公平鎖的lock方法:
java.util.concurrent.locks.ReentrantLock.NonfairSync
/**
* 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() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
總結
通過觀察可以發現:公平鎖的lock方法在進行cas判斷時多了一個hasQueuedPredecessors()方法,它會在AQS隊列中沒有中沒有線程的情況下才會申請鎖,而不像非公平鎖一樣,非公平鎖一來不管AQS裏是否有排隊的線程就直接申請鎖。
最近嚐到了不受公司領導重視的滋味,在公司沒有存在感,開會不叫我,分配任務不分配給我
看着身邊的同事都有開發任務時,我只能羨慕
從事程序員以來也是第一次感受到這種情況,讓我一次次懷疑自己
加油吧!小老弟!