參考博客:https://www.jianshu.com/p/43e8e3a8b688
引入
synchronized使用詳解中提到過除了使用 synchronized關鍵字來實現線程間的互斥,從而保證線程同步,並在其中提到了lock接口,然後最近面試也被問到線程安全方面的問題,所以這塊我們還是需要了解一下的。
Lock簡介
Lock是一種控制多線程訪問共享資源的工具
要訪問共享資源,必先獲取鎖
同一時刻只能有一個線程獲取鎖
一般而言,lock都是排他性(即只能當前線程)的訪問共享資源
但是,也有一些鎖可以允許對共享資源的併發訪問,比如ReadWriteLock,就能解決使用synchronized關鍵字無法解決的問題。
一般而言,基本使用
//創建對應鎖的實例
Lock l = lockInstance;
l.lock();
try {
//TODO 訪問共享資源
} finally {
//釋放鎖
l.unlock();
}
接口api
package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
/**
* @since 1.5
* @author Doug Lea
*/
public interface Lock {
/**
* 獲取鎖.調用該方法的線程會得到鎖。
* 如果沒有爭搶到,進入阻塞狀態。
*/
void lock();
/**
* 可中斷的獲取鎖,該方法可以響應中斷(可以中斷當前線程)。
*/
void lockInterruptibly() throws InterruptedException;
/**
* 嘗試非阻塞地獲取鎖。
* 調用後方法立即返回:
* 如果能正常獲取到鎖,則立即返回true
* 如果沒能正常獲取到鎖,則立即返回false。
*
* 對該方法的調用可能是這樣的:
* Lock lock = ...;
* if (lock.tryLock()) {
* try {
* // 獲取到鎖進行操作
* } finally {
* lock.unlock();
* }
* } else {
* // 未獲取到鎖進行操作
* }
*
*/
boolean tryLock();
/**
* 在指定的超時時間內獲取鎖。
*
* 如果鎖可以立即獲得,則直接返回true。
* 否則,在以下三種情況下會返回:
* 1. 在指定的超時時間內獲取到了鎖(true)
* 2. 在指定的超時時間內線程被interrupt()了(false)
* 3. 超時(false)
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
* 釋放鎖.
*/
void unlock();
/**
* 返回和當前Lock對象綁定的Condition實例。
*/
Condition newCondition();
}
Lock常見的實現類
ReentrantLock 可重入鎖
什麼是可重入?即一個線程對資源的重複加鎖。在調用lock()方法時,已經獲取到鎖的線程,能夠再次調用lock()方法獲取鎖而不被阻塞。
公平性/非公平行支持
public ReentrantLock() {
sync = new NonfairSync();
}
//fair:是否支持公平性獲取鎖
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock主要是使用FairSync()和NonfairSync()來確定是使用公平鎖還是非公平鎖。默認情況下,如果不指定,則爲非公平鎖。
公平鎖和非公平鎖。公平鎖表示線程獲取鎖的順序是按照線程加鎖的順序來分配的,即是一個先進先出的FIFO的隊列;而非公平鎖就是一種隨機的分配,不關乎先後順序,非公平鎖會隨機分配一個處於就緒狀態的線程來獲取鎖並執行相應的邏輯。 公平鎖和非公平鎖各有優劣,公平鎖需要維護一個隊列,需要知道各個線程進入就緒狀態的順序,而非公平鎖則不需要,只需要每次隨機分配一個已經處於就緒狀態的線程即可。
ReentrantLock總結
排他性
可重入
公平/非公平性支持
ReentrantReadWriteLock可重入讀寫鎖
ReentrantLock和synchronized全部是一種排他鎖,也就是說,同一時間只有一個線程在執行加鎖後面的任務。這樣雖然可以保證了實例變量的線程安全性,但是效率也會相對低下,因爲有時候我們只是需要讀取數據,但是又怕讀到的數據是髒數據,所以才被逼無奈加鎖的。
那麼實際上,JDK同樣考慮到這一點,提供了一種讀寫鎖ReentrantReadWriteLock的支持,它可以限定讀鎖和寫鎖,在不同的鎖下有不同的互斥效果。 這種鎖既然叫讀寫鎖,表示有兩個鎖,一個是讀操作相關的,是共享鎖;另一個是寫操作相關的鎖,它就是個單純的排他鎖。也就是說,多個讀鎖之間是不互斥的,而讀鎖和寫鎖之間或者多個寫鎖之間是互斥的。
總結來說,規律如下:
讀鎖-寫鎖:互斥。
寫鎖-寫鎖:互斥。
讀鎖-讀鎖:共享不互斥。
舉個栗子
和參考博客一樣,這裏就不貼代碼了,其實使用還是很簡單的。
Condition接口
從Lock中提供的基本API可以看到,它只負責獲取鎖和釋放鎖。如果需要實現類似wait()/notify()這種等待/通知的機制,還需要藉助Condition接口。
Condition接口提供了最基本的實現等待/通知機制的API,但是它比wait()/notify()更強大,這裏先介紹一些它最基本的API。
await():讓當前線程進入WAITING狀態,同時釋放鎖,類似wait()的作用。
signal():通知某個處於WAITING狀態的線程,可以繼續獲取鎖執行。類似notify()的作用。 signalAll():通知所有處於WAITING狀態的線程,開始爭搶鎖然後執行。類似notifyAll()的作用。
寫在最後
還有其他的實現類以及Condition接口的具體使用,這裏就不展開了,以後如果有用到,再來補充。