併發編程技術(四)

上一節講了sychronized用法及實現原理,詳細請回顧《 》,今天我們繼續講解ReentrantLock。

ReentrantLock的介紹

ReentrantLock重入鎖,是實現Lock接口的一個類,也是在實際編程中使用頻率很高的一個鎖,支持重入性,表示能夠對共享資源能夠重複加鎖,即當前線程獲取該鎖再次獲取不會被阻塞。在java關鍵字synchronized隱式支持重入性(關於synchronized上節已經講過),synchronized通過獲取自增,釋放自減的方式實現重入。與此同時,ReentrantLock還支持公平鎖和非公平鎖兩種方式。

lock 與sychronized的區別

  1. sychronized是一個關鍵字,Lock是一個對象
  2. sychronized的鎖釋放是被動的,只有執行完或出現異常時才會釋放。lock可以判斷鎖的判斷
  3. lock可以指定是公平鎖或非公平鎖,sychronized是非公平鎖,不能指定

重入讀寫鎖 ReentrantReadWriteLock的作用是什麼

當我們在執行讀操作的時候,它首先獲取一個讀鎖。在併發訪問時,讀它不會被阻塞,因為讀不會改變數據本身的狀態。寫操作時線程必須要獲取一個寫鎖,當其它線程持有寫鎖的情況下,當前線程在寫操作時會獲取不到這個鎖,那麼這個鎖會被阻塞,只有這個鎖被釋放以後其它的寫才可以繼續。同樣,當前線程獲得到寫鎖後,其它線程在讀操作時會被阻塞,因為在寫鎖時有可能數據會發生變化。在寫鎖釋放後讀操作才可以進入讀取數據。

其實就是通過讀寫鎖來提升併發操作的性能,使用讀寫鎖能夠在讀多寫少的場景下,大大提升我們操作性能。

讀-讀是共享

讀-寫是不共享

寫-讀是不共享

寫-寫是不共享

談到ReentrantLock,不得不談的AbstractQueuedSynchronizer。

ReentrantLock實現圖


併發編程技術(四)


ReentrantLock非公平鎖和公平鎖的源碼

//非公平鎖
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//公平鎖
final void lock() {
acquire(1);
}

ReentrantLock提供兩個構造方法

//默認是非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
//
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}


什麼是公平和非公平鎖

公平鎖每次獲取到鎖為同步隊列中的第一個節點,保證請求資源時間上的絕對順序,而非公平鎖有可能剛釋放鎖的線程下次繼續獲取該鎖,則有可能導致其他線程永遠無法獲取到鎖,造成“飢餓”現象。

公平鎖為了保證時間上的絕對順序,需要頻繁的上下文切換,而非公平鎖會降低一定的上下文切換,降低性能開銷。因此,ReentrantLock默認選擇的是非公平鎖,則是為了減少一部分上下文切換,保證了系統更大的吞吐量。

Condition線程間通信

他提供的是在JDK層面上實現對鎖的控制。在多線程中用來協調通信的工具,跟wait/notify是完全一樣的。也就是可以讓某些線程在某個條件的情況下,只有滿足某個條件的時候線程才會被喚醒,若不滿足,則不會喚醒。

Condition的實現原理和基本使用方法

1、Condition提供了await()方法將當前線程阻塞,並提供signal()方法支持另外一個線程將已經阻塞的線程喚醒。

2、Condition需要結合Lock使用

3、線程調用await()方法前必須獲取鎖,調用await()方法時,將線程構造成節點加入等待隊列,同時釋放鎖,並掛起當前線程

4、其他線程調用signal()方法前也必須獲取鎖,當執行signal()方法時將等待隊列的節點移入到同步隊列,當線程退出臨界區釋放鎖的時候,喚醒同步隊列的首個節點

為什麼在調用condition.await()和condition.signal()時要獲取鎖?

是因為需要通過這個線程等待釋放後仍然能夠競爭鎖,Condition隊列喚醒後重新加入AQS

AQS的介紹

提供了一個基於FIFO隊列,可以用於構建鎖或者其他相關同步裝置的基礎框架。該同步器(以下簡稱同步器)利用了一個int來表示狀態,期望它能夠成為實現大部分同步需求的基礎。使用的方法是繼承,子類通過繼承同步器並需要實現它的方法來管理其狀態,管理的方式就是通過類似acquire和release的方式來操縱狀態。然而多線程環境中對狀態的操縱必須確保原子性,因此子類對於狀態的把握,需要使用這個同步器提供的以下三個方法對狀態進行操作:

  • getState()
  • setState(int)
  • compareAndSetState(int, int)


AQS定義兩種資源共享方式:Exclusive(獨佔,只有一個線程能執行,如ReentrantLock)和Share(共享,多個線程可同時執行,如Semaphore/CountDownLatch)

AQS中的Node是核心,它是一個鏈表,AQS 有兩個鎖 共享鎖和獨佔鎖

static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;

鏈表的頭節點和尾節點,源碼

private transient volatile Node head;
private transient volatile Node tail;

對應CAS的方法

compareAndSetHead()

compareAndSetTail()

CompareAndSet 的實現是都是由 Unsafe.compareAndSwapObject 實現,其中Unsafe是jvm中的後門。

compareAndSetState方法中

  • state=0 表示無鎖
  • state>0表示有鎖
private volatile long state;


併發編程技術(四)


喚醒線程的方式

  • Thread.currentThread().interrupted()
  • LockSupport.unpark()

------------------


分享到:


相關文章: