看到文章的標題是不是很詫異,一個搞技術的為什麼要搞這麼文藝的話題呢?標題說關鎖千重,是不是很形象,我們在開發中的鎖不也是多種多樣麼?
Lock
既然之前說了鎖千重,那鎖到底有多少種,他們的分類又是怎麼區分的,為什麼這麼區分?我來給大家解釋一下。
為什麼加鎖?
面試中有很多時候會問到,為什麼加鎖?加鎖是起到什麼作用?
而實際上在我們的開發過程中會出現併發的情況,比如說兩個人幾乎同時點選了某一個按鈕,這個時候就可以簡單的理解成併發,那麼到底誰先誰後? 程式中就很可能出現錯誤,當資源出現共享的時候,就會開始涉及到併發了,這個時候我們就可能會用到鎖了,來鎖住某一個資源,等我用過之後,你才能動。 這就是為什麼使用鎖。
鎖的分類
1.公平鎖/非公平鎖
2.可重入鎖
3.獨享鎖/共享鎖
4.互斥鎖/讀寫鎖
5.樂觀鎖/悲觀鎖
6.分段鎖
7.偏向鎖/輕量級鎖/重量級鎖
8.自旋鎖
第一次分享,我們就先說這個公平鎖和非公平鎖。之後會在後序的文章中繼續解析!
何為公平?何為非公平?在我們日常生活中的理解不就是對等的就是公平,不對等的就是不公平? 其實差不多的。
公平鎖是指多個執行緒按照申請鎖的順序來獲取鎖
非公平鎖是指多個執行緒獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的執行緒比先申請的執行緒優先獲取鎖。有可能,會造成優先順序反轉或者飢餓現象。
在JAVA的程式碼中什麼是公平鎖什麼又是非公平的鎖呢?
一種是使用Java自帶的關鍵字synchronized對相應的類或者方法以及程式碼塊進行加鎖,
而另一種是ReentrantLock,前者只能是非公平鎖,而後者是預設非公平但可實現公平的一把鎖。
上面的類圖看起來很不舒服,因為關於ReentrantLock這個鎖,確實是沒有很好的配圖,我們可以自己畫出來理解一下
我們先看非公平鎖,我畫圖大家理解一下,就像公共廁所,不要嫌棄噁心,但是絕對容易理解
上面這幅圖,加入說圖中的管理員是Lock,然後現在A來了,說我要去廁所,這時候管理員一看,廁所沒人,那好你進去把,然後A就進去了。
這個時候WC裡面是有A的,正在進行式,這時候B來了,B也想去廁所
但是這個時候WC裡面是有A的,然後管理員Lock看了一下,裡面有人,排隊把。。。
然後這B就得憋著,去進入佇列去排隊,然後又來了個C,這時候A還在裡面,C只能也去排隊,
就是這個樣子的
這時候又過了一小會來了個D,也想去WC,這時候A恰好結束了,
這時候非公平鎖就上場了,Lock管理員一看,裡面沒人,D你進去把,這時候就是這樣的。
然後這時候因為A出來了之後說,我結束了,然後B正準備進去的時候,裡面又有了D,這就是出現了非公平鎖
而非公平鎖的呼叫就是 A被Lock --> B去wait--> C在B後面wait --> A結束了,unlock -->非公平鎖compareAndSetState(0, acquires),裡面沒人 -->D被Lock 這時候A釋放鎖lock.release,-->結果晚了一步,沒進去,只能繼續等著
這就是非公平鎖的簡單的一種理解,道理其實挺簡單的, 而公平鎖就不一樣了,Lock管理員會告訴新來的D,你前面已經有好幾個在排號的了,你想進去,去後邊排隊。
公平鎖的呼叫是這樣子的。
前面其實都是一樣的,但是當D來的時候就不一樣了,
D來的時候-->lock知道了-->直接呼叫hasQueuedPredecessors()告訴D有人在排隊,你去後邊排隊,這樣子下來就實實在在的保證了公平了。
接下來我們看看ReentrantLock的原始碼實現,然後解釋一下
public class ReentrantLock implements Lock, java.io.Serializable {
/*同步器提供所有實現機制 */
private final Sync sync;
/**
此鎖的同步控制基礎。將轉換為下面的公平和非公平版本。使用AQS狀態表示鎖定的保持數。
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
執行{@link Lock#lock}。子類化x的主要原因是允許非公平版本的快速路徑。
*/
abstract void lock();
/**
* 執行非公平的tryLock。 tryAcquire在子類中實現,但兩者都需要tryf方法的非公平嘗試。
注意在這裡執行的是非公平鎖也就是說在判斷方法compareAndSerState的時候,
新的執行緒可能搶佔已經排隊的執行緒的鎖的使用權
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//狀態為0,說明當前沒有執行緒佔有鎖
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;
}
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
/**
從流中重構例項(即反序列化它)。
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
}}
上面的原始碼都是說的是非公平鎖的,我們在看看原始碼中對公平鎖又是怎麼進行定義的!
/**
同步物件以進行公平鎖定
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
tryAcquire的公平版本。除非遞迴呼叫或沒有服務員或是第一個,否則不授予訪問許可權。
在這裡有一個hasQueuedPredecessors的方法,就是這個方法來保證我們不論是新的執行緒還是已經在進行排隊的執行緒都順序的去使用鎖
*/
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;
}
}
解釋完了,我們來做個總結 總結一:
1.公平鎖:老的執行緒排隊使用鎖,新執行緒仍然排隊使用鎖。2.非公平鎖:老的執行緒排隊使用鎖;但是新的執行緒是極有可能搶佔已經在排隊的執行緒的鎖。
以後我會更新關於鎖的一些文章,希望大家能夠指點一下,也能夠共同的討論進步,謝謝!
Java 極客技術公眾號,是由一群熱愛 Java 開發的技術人組建成立,專注分享原創、高質量的 Java 文章。如果您覺得我們的文章還不錯,請幫忙讚賞、在看、轉發支援,鼓勵我們分享出更好的文章。
關注公眾號,大家可以在公眾號後臺回覆“部落格園”,免費獲得作者 Java 知識體系/面試必看資料。