一、背景
阿裡技術的公衆發了一篇文章
《誰是代碼界3%的王者?》,
提到“在Java代碼界,有些陷阱外表看起來是個青銅實際上是王者,據說97%工程師會被“秒殺””
給出了5道題,非常考驗基礎。
本文簡單解讀第五題。
二、代碼
給出的示例代碼:
public class LockTest {
private final static Lock lock = new ReentrantLock();
public static void main(String[] args) {
try {
lock.tryLock();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

給出的問題:
下列哪種說法是錯誤的:
A: lock是非公平鎖
B: finally代碼塊不會抛出異常
C: tryLock擷取鎖失敗則直接往下執行
答案應該是B(不過問題和源碼并不是很比對)。
三、解讀
我們改造一些上面的源代碼,新增一個子線程,讓子線程先擷取鎖,我們看會發生什麼:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
private final static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
if (lock.tryLock()) {
System.out.println("子線程擷取鎖"+"成功");
try {
TimeUnit.SECONDS.sleep(6);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("子線程釋放鎖");
lock.unlock();
}
}
});
thread.start();
TimeUnit.SECONDS.sleep(1);
try {
// 有可能沒擷取到鎖
boolean tryLock = lock.tryLock();
System.out.println("主線程擷取鎖"+(tryLock?"成功":"失敗"));
} catch (Exception e) {
e.printStackTrace();
} finally {
// 有可能沒擷取到鎖調用了釋放
System.out.println("主線程釋放鎖");
lock.unlock();
}
}
}
運作結果:
子線程擷取鎖成功
主線程擷取鎖失敗
主線程釋放鎖
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
at com.chujianyun.common.style.LockTest.main(LockTest.java:41)
子線程釋放鎖
接下來我們分析原因。
先看A選項: lock是非公平鎖,我們看到lock的類型為ReentrantLock。
還是老規矩,源代碼代碼:
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
很明顯預設的空參數構造方法建立的的确是非公平鎖。
另外我們發現它還提供了構造方法支援指定是否為公平鎖。
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平鎖和非公平鎖的主要差別:
更詳細内容參見
《不可不說的Java“鎖”事》再看C選項tryLock擷取鎖失敗則直接往下執行。
繼續看源碼
java.util.concurrent.locks.ReentrantLock#tryLock()
/**
* Acquires the lock only if it is not held by another thread at the time
* of invocation.
*
* <p>Acquires the lock if it is not held by another thread and
* returns immediately with the value {@code true}, setting the
* lock hold count to one. Even when this lock has been set to use a
* fair ordering policy, a call to {@code tryLock()} <em>will</em>
* immediately acquire the lock if it is available, whether or not
* other threads are currently waiting for the lock.
* This "barging" behavior can be useful in certain
* circumstances, even though it breaks fairness. If you want to honor
* the fairness setting for this lock, then use
* {@link #tryLock(long, TimeUnit) tryLock(0, TimeUnit.SECONDS) }
* which is almost equivalent (it also detects interruption).
*
* <p>If the current thread already holds this lock then the hold
* count is incremented by one and the method returns {@code true}.
*
* <p>If the lock is held by another thread then this method will return
* immediately with the value {@code false}.
*
* @return {@code true} if the lock was free and was acquired by the
* current thread, or the lock was already held by the current
* thread; and {@code false} otherwise
*/
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
從源碼裡可以看出失敗會傳回false,而且沒有抛異常。
另外看Lock接口的注釋可以發現,原示例中的用法不規範:
java.util.concurrent.locks.Lock#tryLock()
上面的專門給出了鎖的典型用法:
Lock lock = ...;
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// perform alternative actions
}
從代碼我們可以看出,我們通過lock.tryLock的傳回值判斷加鎖是否成功,來分别執行成功和失敗的邏輯。
并且加鎖成功的邏輯中解鎖的代碼要放到finally代碼塊中保證異常仍然會釋放鎖。
另外從改造後的例子可以看出,lock.tryLock擷取鎖失敗會繼續執行。
我們再看B選項: finally代碼塊不會抛出異常
根據我們改造的源碼可以看到,finally代碼塊可能抛出IllegalMonitorStateException。
同樣的我們看源碼:
java.util.concurrent.locks.ReentrantLock#unlock
/**
* Attempts to release this lock.
*
* <p>If the current thread is the holder of this lock then the hold
* count is decremented. If the hold count is now zero then the lock
* is released. If the current thread is not the holder of this
* lock then {@link IllegalMonitorStateException} is thrown.
*
* @throws IllegalMonitorStateException if the current thread does not
* hold this lock
*/
public void unlock() {
sync.release(1);
}
這裡寫的很清楚,如果目前線程并不持有此鎖時,調用unlock會報IllegalMonitorStateException。
四、拓展
4.1 正确用法
我們去java.util.concurrent.locks.ReentrantLock的源碼頂部看注釋發現,官方給出了一個标準用法範例:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // 直到條件滿足,否則一直阻塞
try {
// ... 函數體
} finally {
lock.unlock()
}
}
}}
為什麼不把lock.lock()放在try裡呢??
因為如果放到try内,且前面有其他代碼出現異常,并沒有擷取到鎖,則finnaly解鎖會抛異常。
4.2 lock.lock()擷取失敗後面會不會執行?
java.util.concurrent.locks.ReentrantLock#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();
}
通過注釋就可以看到如果鎖被其他線程所持有,則目前線程被阻塞直到擷取到鎖,此時鎖的持有數量也會被設定為1。
五、總結
可以說JDK源碼是我們學習JDK類的一個最關鍵且最好的素材。
我們要學習研究某個類或者方法的時候,超級!超級!超級!建議先進源碼去看注釋。
我們學習的時候,開發的時候有困惑的地方盡量都要先去看源碼,不要等面試的時候再去”突擊“!!
創作不易,如果覺得本文對你有幫助,歡迎點贊,歡迎關注我,如果有補充歡迎評論交流,我将努力創作更多更好的文章。