java 一步一步教你手寫ReentrantLock

前言

我們都知道,ReentrantLock 是我們java中經常使用到的鎖,他的內部使用cas機制來進行實現,相比於synchronized 同步關鍵字,在早期版本有很大的性能提升,首先我們看一下synchronized的實現原理並且進行一下對比

synchronized實現原理

實現原理: JVM 是通過進入、退出對象監視器( Monitor )來實現對方法、同步塊的同步的。

具體實現是在編譯之後在同步方法調用前加入一個 monitor.enter 指令,在退出方法和異常處插入 monitor.exit 的指令。

其本質就是對一個對象監視器( Monitor )進行獲取,而這個獲取過程具有排他性從而達到了同一時刻只能一個線程訪問的目的。

而對於沒有獲取到鎖的線程將會阻塞到方法入口處,直到獲取鎖的線程 monitor.exit 之後才能嘗試繼續獲取鎖。

使用 javap -c Synchronize 可以查看編譯之後的具體信息。


public class com.crossoverjie.synchronize.Synchronize {
public com.crossoverjie.synchronize.Synchronize();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

public static void main(java.lang.String[]);
Code:
0: ldc #2 // class com/crossoverjie/synchronize/Synchronize
2: dup
3: astore_1
**4: monitorenter**
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #4 // String Synchronize
10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

13: aload_1
**14: monitorexit**
15: goto 23
18: astore_2
19: aload_1
20: monitorexit
21: aload_2
22: athrow
23: return
Exception table:
from to target type
5 15 18 any
18 21 18 any
}
/<init>

可以看到在同步塊的入口和出口分別有 monitorenter,monitorexit 指令。

鎖優化

synchronized 很多都稱之為重量鎖,JDK1.6 中對 synchronized 進行了各種優化,為了能減少獲取和釋放鎖帶來的消耗引入了偏向鎖和輕量鎖。

而我們今天要實現的就是一種輕量級鎖的機制,那麼我們直奔主題

首先我們實現鎖,就要implements Lock接口

@Override
public void lock() {

}
@Override
public void unlock() {
}

既然要實現鎖,那麼我們必須要知道當前鎖的狀態,所以,我們使用一個原子引用對當前持有鎖的線程進行控制,若為nullptr 則認為當前鎖沒有線程持有

AtomicReference<thread> owner = new AtomicReference<>(null);
/<thread>

初始化為null

當我們要對線程加鎖的時候,我們將其設置為要加鎖的線程

當我們要對線程釋放鎖的時候。我們將其設置為nullper

@Override
public void lock() {
owner.set(Thread.currentThread());
}
@Override
public void unlock() {
owner.set(null);
}

目前變成這樣了,但是現在出現了一個問題,加鎖時候不能判斷owner是否有線程持有,如果有線程持有就不能加鎖,只能等待,同時,釋放鎖也有這樣的問題,如果當前線程不是鎖的持有者,那麼就是給其他線程的鎖給釋放了,所以我們要事先判斷

@Override
public void lock() {
if (owner.get() == null) {

owner.set(Thread.currentThread());
}
}
@Override
public void unlock() {
if (owner.get() == Thread.currentThread()) {
owner.set(null);
}
}

這樣的話,我們就可以在對其加鎖前進行判斷是否能夠加鎖,但是這樣的代碼卻出現了線程安全的問題,因為判斷和賦值並不是原子操作,所以,我們需要利用atomic類特性進行進一步改造

@Override
public void lock() {
if (owner.compareAndSet(null,Thread.currentThread()))
{
//獲取鎖成功
}
else
{
//獲取鎖失敗
}
}
@Override
public void unlock() {
if (owner.compareAndSet(Thread.currentThread(),null))
{
//釋放瑣成功
}
else
{
//釋放鎖失敗
}
}

這樣通過compareandset機制可以保證一個操作的原子性,但是如果獲取鎖失敗,我們需要進行一個等待,等待上一個線程釋放鎖之後進行一個嘗試,那麼我們就需要一個隊列拉進行線程的保存

else {
waitqueue.offer(Thread.currentThread());
LockSupport.park();
//獲取鎖失敗
}j

將線程存入隊列,使用park阻塞,等待喚醒

if (owner.compareAndSet(null, Thread.currentThread())) {
//獲取鎖成功
} else {
waitqueue.offer(Thread.currentThread());
LockSupport.park();
lock();
//獲取鎖失敗
}
@Override
public void unlock() {
if (owner.compareAndSet(Thread.currentThread(), null)) {
LockSupport.unpark(waitqueue.poll());
} else {
//釋放鎖失敗,只有一種可能,當前線程不持有鎖
}
}

這樣的話我們的線程就可以正確的喚醒了,很簡單是吧


分享到:


相關文章: