lock
是一個接口,而synchronized
是在JVM
層面實現的。synchronized
釋放鎖有兩種方式:java
jvm
會讓線程釋放鎖。lock
鎖的釋放,出現異常時必須在finally
中釋放鎖,否則容易形成線程死鎖。lock
顯式獲取鎖和釋放鎖,提供超時獲取鎖、可中斷地獲取鎖。node
synchronized
是以隱式地獲取和釋放鎖,synchronized
沒法中斷一個正在等待獲取鎖的線程。安全
synchronized
原始採用的是CPU
悲觀鎖機制,即線程得到的是獨佔鎖。獨佔鎖意味着其餘線程只能依靠阻塞來等待線程釋放鎖。而在CPU
轉換線程阻塞時會引發線程上下文切換,當有不少線程競爭鎖的時候,會引發CPU
頻繁的上下文切換致使效率很低。bash
Lock
用的是樂觀鎖方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操做,若是由於衝突失敗就重試,直到成功爲止。樂觀鎖實現的機制就是CAS
操做。多線程
具體的悲觀鎖和樂觀鎖的詳細介紹請參考這篇文章[]併發
在JDK5
中增長了一個Lock
接口實現類ReentrantLock
.它不只擁有和synchronized
相同的併發性和內存語義,還多了鎖投票,定時鎖,等候和中斷鎖等.它們的性能在不一樣的狀況下會有不一樣。jvm
在資源競爭不是很激烈的狀況下,synchronized
的性能要因爲ReentrantLock
,可是在資源競爭很激烈的狀況下,synchronized
的性能會降低得很是快,而ReentrantLock
的性能基本保持不變.ide
接下來咱們會進一步研究ReentrantLock
的源代碼,會發現其中比較重要的得到鎖的一個方法是compareAndSetState
。源碼分析
在閱讀源碼的成長的過程當中,有不少人會遇到不少困難,一個是源碼太多,另外一方面是源碼看不懂。在閱讀源碼方面,我提供一些我的的建議:性能
接下來進入閱讀lock的源碼部分,在lock的接口中,主要的方法以下:
public interface Lock {
// 加鎖
void lock();
// 嘗試獲取鎖
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 解鎖
void unlock();
}
複製代碼
在lock接口的實現類中,最主要的就是ReentrantLock
,來看看ReentrantLock
中lock()
方法的源碼:
// 默認構造方法,非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
// 構造方法,公平鎖
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
// 加鎖
public void lock() {
sync.lock();
}
複製代碼
在初始化lock實例對象的時候,能夠提供一個boolean的參數,也能夠不提供該參數。提供該參數就是公平鎖,不提供該參數就是非公平鎖。
非公平鎖就是不按照線程先來後到的時間順序進行競爭鎖,後到的線程也可以獲取到鎖,公平鎖就是按照線程先來後到的順序進行獲取鎖,後到的線程只能等前面的線程都獲取鎖完畢才執行獲取鎖的操做,執行有序。
咱們來看看lock()這個方法,這個有區分公平鎖和非公平鎖,這個二者的實現不一樣,先來看看公平鎖,源碼以下:
// 直接調用 acquire(1)
final void lock() {
acquire(1);
}
複製代碼
咱們來看看acquire(1)的源碼以下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
複製代碼
這裏的判斷條件主要作兩件事:
tryAcquire(arg)
嘗試的獲取鎖acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
就將當前的線程加入到存儲等待線程的隊列中。其中tryAcquire(arg)
是嘗試獲取鎖,這個方法是公平鎖的核心之一,它的源碼以下:
protected final boolean tryAcquire(int acquires) {
// 獲取當前線程
final Thread current = Thread.currentThread();
// 獲取當前線程擁有着的狀態
int c = getState();
// 若爲0,說明當前線程擁有着已經釋放鎖
if (c == 0) {
// 判斷線程隊列中是否有,排在前面的線程等待着鎖,如果沒有設置線程的狀態爲1。
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 設置線程的擁有着爲當前線程
setExclusiveOwnerThread(current);
return true;
}
// 如果當前的線程的鎖的擁有者就是當前線程,可重入鎖
} else if (current == getExclusiveOwnerThread()) {
// 執行狀態值+1
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 設置status的值爲nextc
setState(nextc);
return true;
}
return false;
}
複製代碼
在tryAcquire()
方法中,主要是作了如下幾件事:
hasQueuedPredecessors()
再判斷等待線程隊列中,是否存在排在前面的線程。compareAndSetState(0, acquires)
設置當前的線程狀態爲1。setExclusiveOwnerThread(current)
current == getExclusiveOwnerThread()
判斷鎖的擁有者的線程,是否爲當前線程,實現鎖的可重入。公平鎖的tryAcquire()
,實現的原理圖以下:
acquireQueued()
方法,該方法是將線程加入等待的線程隊列中,源碼以下:
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
return interrupted;
}
// 在獲取鎖失敗後,應該將線程Park(暫停)
if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
複製代碼
acquireQueued()
方法主要執行如下幾件事:
p == head && tryAcquire(arg)
,則跳出循環,即獲取鎖成功。shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()
就會將線程暫停。在acquire(int arg)
方法中,最後如果條件成立,執行下面的源碼:
selfInterrupt();
// 實際執行的代碼爲
Thread.currentThread().interrupt();
複製代碼
即嘗試獲取鎖失敗,就會將鎖加入等待的線程隊列中,並讓線程處於中斷等待。公平鎖lock()
方法執行的原理圖以下:
有了流程圖,在後面的實現本身的東西才能一步一步的進行。這也是閱讀源碼的必要之一。
在lock()
方法,其實在lock()方法中,已經包含了兩方面:
lock()
。tryAquire()
。接下來,咱們來看一下unlock()方法的源碼。
public void unlock() {
sync.release(1);
}
複製代碼
直接調用release(1)
方法,來看release
方法源碼以下:
public final boolean release(int arg) {
// 嘗試釋放當前節點
if (tryRelease(arg)) {
// 取出頭節點
Node h = head;
if (h != null && h.waitStatus != 0)
// 釋放鎖後要即便喚醒等待的線程來獲取鎖
unparkSuccessor(h);
return true;
}
return false;
}
複製代碼
經過調用tryRelease(arg)
,嘗試釋放當前節點,如果釋放鎖成功,就會獲取的等待隊列中的頭節點,就會即便喚醒等待隊列中的等待線程來獲取鎖。接下來看看tryRelease(arg)
的源碼以下:
// 嘗試釋放鎖
protected final boolean tryRelease(int releases) {
// 將當前狀態值-1
int c = getState() - releases;
// 判斷當前線程是不是鎖的擁有者,若不是直接拋出異常,非法操做,直接一點的解釋就是,你都沒有擁有鎖,還來釋放鎖,這不是騙人的嘛
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//執行釋放鎖操做 1.若狀態值=0 2.將當前的鎖的擁有者設爲null
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 從新更新status的狀態值
setState(c);
return free;
}
複製代碼
總結上面的幾個方法,unlock釋放鎖方法的執行原理圖以下:
對於非公平鎖與公平鎖的區別,在非公平鎖嘗試獲取鎖中不會執行hasQueuedPredecessors()
去判斷是否隊列中還有等待的前置節點線程。
以下面的非公平鎖,嘗試獲取鎖nonfairTryAcquire()
源碼以下:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 直接就將status-1,並不會判斷是否還有前置線程在等待
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;
}
複製代碼
以上就是公平鎖和非公平鎖的主要的核心方法的源碼,接下來咱們實現本身的一個鎖,首先依據前面的分析中,要實現本身的鎖,擁有的鎖的核心屬性以下:
status
,0爲未佔用鎖,1未佔用鎖,而且是線程安全的。lock
鎖的核心的Api
以下:
lock
方法trylock
方法unlock
方法依據以上的核心思想來實現本身的鎖,首先定義狀態值status,使用的是AtomicInteger
原子變量來存放狀態值,實現該狀態值的併發安全和可見性。定義以下:
// 線程的狀態 0表示當前沒有線程佔用 1表示有線程佔用
AtomicInteger status =new AtomicInteger();
複製代碼
接下來定義等待線程隊列,使用LinkedBlockingQueue
隊列來裝線程,定義以下:
// 等待的線程
LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<Thread>();
複製代碼
最後的屬性未當前鎖的擁有者,直接就用Thread
來封裝,定義以下:
// 當前線程擁有者
Thread ownerThread =null;
複製代碼
接下來定義lock()
方法,依據上面的源碼分析,在lock
方法中主要執行的幾件事以下:
@Override
public void lock() {
// TODO Auto-generated method stub
// 嘗試獲取鎖
if (!tryLock()) {
// 獲取鎖失敗,將鎖加入等待的隊列中
waitersQueue.add(Thread.currentThread());
// 死循環處理隊列中的鎖,不斷的獲取鎖
for (;;) {
if (tryLock()) {
// 直到獲取鎖成功,將該線程從等待隊列中刪除
waitersQueue.poll();
// 直接返回
return;
} else {
// 獲取鎖不成功,就直接暫停等待。
LockSupport.park();
}
}
}
}
複製代碼
而後是trylock
方法,依據上面的源碼分析,在trylock
中主要執行的如下幾件事:
@Override
public boolean tryLock() {
// 判斷是否有現成佔用
if (status.get()==0) {
// 執行狀態值加1
if (status.compareAndSet(0, 1)) {
// 將當前線程設置爲鎖擁有者
ownerThread = Thread.currentThread();
return true;
} else if(ownerThread==Thread.currentThread()) {
// 實現鎖可重入
status.set(status.get()+1);
}
}
return false;
}
複製代碼
最後就是unlock方法,依據上面的源碼分析,在unlock中主要執行的事情以下:
@Override
public void unlock() {
// TODO Auto-generated method stub
// 判斷當前線程是不是鎖擁有者
if (ownerThread!=Thread.currentThread()) {
throw new RuntimeException("非法操做");
}
// 判斷狀態值是否爲0
if (status.decrementAndGet()==0) {
// 清空鎖擁有着
ownerThread = null;
// 從等待隊列中獲取前置線程
Thread t = waitersQueue.peek();
if (t!=null) {
// 並當即喚醒該線程
LockSupport.unpark(t);
}
}
}
複製代碼
以上就是實現本身的非公平的可重入鎖,lock的源碼其實並不複雜,只要認真看都能看懂,在閱讀源碼的過程當中,會遇到比較複雜的問題。遇到問題不要慌,網上查詢資料,相信不少都能找到答案,由於java的生態如此完善,幾乎90%的東西網上都會有,只要沉得住氣,相信必定會有所收穫。