【Java多線程】ReentrantLock與Condition


ReentrantLock 與synchronized異同

1.引入

經過之前的學習,你肯定知道鎖就用synchronized,但今天我再介紹一種鎖-Lock

2.底層實現

synchronized是基於JVM層面實現的,而Lock是基於JDK層面實現的

3.使用

lock不僅要上鎖還要釋放鎖,如果忘記釋放鎖就會產生死鎖的問題,所以,通常需要在finally中進行鎖的釋放。但是synchronized的使用十分簡單

4.公平鎖與非公平鎖

  • 鎖Lock分為公平鎖和非公平鎖,默認是非公平的,公平鎖表示線程獲取鎖的順序按照線程加鎖的順序來分配,即先來先得的FIFO先進先出順序。而非公平鎖就是一種獲取鎖的搶占機制,是隨機獲得鎖的,和公平鎖不一樣就是先來不一定先得到鎖。

  • 對於非公平鎖,只要CAS設置同步狀態成功,則表示當前線程獲取了鎖,而公平鎖則不同,他的判斷條件多了hasQueuePredecessors()方法,即加入了同步隊列中當前節點是否有前驅節點的判斷,如果該方法返回true,則表示有線程比更早的請求獲取鎖,因此需要等待前驅線程獲取鎖並釋放之后才能繼續獲取鎖。

ReentrantLock

1.引入

在Java 5.0 之前,協調共享對象的訪問時可以使用的機制只有synchronized 和volatile 。Java 5.0 后增加了一些新的機制,但並不是一種替代內置鎖的方法,而是當內置鎖不適用時,作為一種可選擇的高級功能。ReentrantLock是synchronized關鍵字的替代品,

2.特點

synchronized使用簡單,功能上比較薄弱,ReentrantLock具備sychronized關鍵字所不具備的特性:

(1) 嘗試非阻塞的獲取鎖:當前線程嘗試獲取鎖,如果這一時刻沒有被其他線程獲取到,則成功獲取並持有鎖。

(2) 能被中斷地獲取鎖:與sychronized不同,獲取到鎖的線程能夠響應中斷,當獲取到鎖的線程被中斷時,中斷的異常將會拋出,同時鎖會被釋放。

(3) 超時獲取鎖:在指定的截止時間之前獲取鎖,如果截止時間到了仍舊無法獲取鎖,則返回。

(4) 是公平鎖

(5)ReentrantLock 實現了Lock 接口,並提供了與synchronized 相同的互斥性和內存可見性。但相較於synchronized 提供了更高的處理鎖的靈活性。

3.實現原理

  • 隊列同步器AbstractQueuedSynchronizer(以下簡稱同步器),是用來構建鎖或者其他同步組 件的基礎框架,它使用了一個int成員變量表示同步狀態,通過內置的FIFO隊列來完成資源獲 取線程的排隊工作。

  • 同步器的主要使用方式是繼承,子類通過繼承同步器並實現它的抽象方法來管理同步狀 態,在抽象方法的實現過程中免不了要對同步狀態進行更改,這時就需要使用同步器提供的3 個方法(getState()、setState(int newState)和compareAndSetState(int expect,int update))來進行操作,因為它們能夠保證狀態的改變是安全的。

  • 子類推薦被定義為自定義同步組件的靜態內部類,同步器自身沒有實現任何同步接口,它僅僅是定義了若干同步狀態獲取和釋放的方法來供自定義同步組件使用,同步器既可以支持獨占式地獲取同步狀態,也可以支持共享式地獲 取同步狀態,這樣就可以方便實現不同類型的同步組件(ReentrantLock、 ReentrantReadWriteLock和CountDownLatch等)。

4.常用方法

方法 功能
int getHoldCount() 查詢當前線程保持此鎖的次數
protected Thread getOwner() 返回目前擁有此鎖的線程,如果此鎖不被任何線程擁有,則返回 null
boolean hasQueuedThreads() 查詢是否有些線程正在等待獲取此鎖
boolean hasWaiters(Condition condition) 查詢是否有些線程正在等待與此鎖有關的給定條件
boolean isFair() 如果此鎖的公平設置為 true,則返回 true
boolean isHeldByCurrentThread() 查詢當前線程是否保持此鎖
boolean isLocked() 查詢此鎖是否由任意線程保持
void lock() 獲取鎖
void lockInterruptibly() 如果當前線程未被中斷,則獲取鎖
Condition newCondition() 返回用來與此 Lock 實例一起使用的 Condition 實例
boolean tryLock() 僅在調用時鎖未被另一個線程保持的情況下,才獲取該鎖
boolean tryLock(long timeout, TimeUnit unit) 如果鎖在給定等待時間內沒有被另一個線程保持,且當前線程未被中斷,則獲取該鎖
void unlock() 試圖釋放此鎖

5.實例

例:

public class LockSynchronized {
public static void main(String[] args) {
OutPut o = new OutPut();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
o.output("Dream it possible!");
}
}
}).start();

new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
o.output("We don't talk anymore!");
}
}
}).start();
}

static class OutPut{
Lock lk = new ReentrantLock();
public void output(String name) {
int len = name.length();
lk.lock();
try {
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}finally {
lk.unlock();
}

}
}
}

輸出結果:

Dream it possible!
We don't talk anymore!
Dream it possible!
We don't talk anymore!
Dream it possible!
We don't talk anymore!
We don't talk anymore!
Dream it possible!
//一直在輸出.....

分析:從結果可以看出,每次輸出一整句,而不是半句或者完整的一句,說明加鎖成功

Condition 控制線程通信

1.Condition 接口描述了可能會與鎖有關聯的條件變量。這些變量在用法上與使用Object.wait 訪問的隱式監視器類似,但提供了更強大的功能。需要特別指出的是,單個Lock 可能與多個Condition 對象關聯。為了避免兼容性問題,Condition 方法的名稱與對應的Object 版本中的不同。Condition是依賴Lock對象的

2.在Condition 對象中,與wait、notify 和notifyAll 方法對應的分別是await、signal 和signalAll。

3.Condition 實例實質上被綁定到一個鎖上。要為特定Lock 實例獲得Condition 實例,請使用其newCondition() 方法。

4.Condition內部有一個等待隊列,等待隊列是一個FIFO的隊列,在隊列中的每個節點都包含了一個線程引用,該線程就是 在Condition對象上等待的線程,如果一個線程調用了Condition.await()方法,那么該線程將會 釋放鎖、構造成節點加入等待隊列並進入等待狀態。事實上,節點的定義復用了同步器中節點 的定義,也就是說,同步隊列和等待隊列中節點類型都是同步器的靜態內部類。

5.Condition能夠更加精細的控制多線程的休眠與喚醒。對於同一個鎖,可以創建多個Condition,在不同的情況下使用不同的Condition。

6.常用方法:

方法 功能
void await() 造成當前線程在接到信號或被中斷之前一直處於等待狀態
boolean await(long time, TimeUnit unit) 造成當前線程在接到信號、被中斷或到達指定等待時間之前一直處於等待狀態
long awaitNanos(long nanosTimeout) 造成當前線程在接到信號、被中斷或到達指定等待時間之前一直處於等待狀態
void awaitUninterruptibly() 造成當前線程在接到信號之前一直處於等待狀態
boolean awaitUntil(Date deadline) 造成當前線程在接到信號、被中斷或到達指定最后期限之前一直處於等待狀態
void signal() 喚醒一個等待線程
void signalAll() 喚醒所有等待線程

生產者消費者模型

之前介紹了利用wait和notify 方法實現的生產者消費者模型,既然學習了ReentrantLock與Condition,那我就利用ReentrantLock與Condition實現的一個生產者消費者模式

例:

public class AlternateTest {
public static void main(String[] args) {
AlternateDemo ad = new AlternateDemo();

new Thread(new Runnable() {
@Override
public void run() {

for (int i = 1; i <= 5; i++) {
ad.loopA(i);
}

}
}, "A").start();

new Thread(new Runnable() {
@Override
public void run() {

for (int i = 1; i <= 5; i++) {
ad.loopB(i);
}

}
}, "B").start();

new Thread(new Runnable() {
@Override
public void run() {

for (int i = 1; i <= 5; i++) {
ad.loopC(i);

System.out.println("-----------------------------------");
}

}
}, "C").start();
}

}

class AlternateDemo{

private int number = 1; //當前正在執行線程的標記

private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();

//循環第幾輪
public void loopA(int totalLoop){
lock.lock();

try {
//1. 判斷
if(number != 1){
condition1.await();
}

//2. 打印
for (int i = 1; i <= 1; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
}

//3. 喚醒
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void loopB(int totalLoop){
lock.lock();

try {
//1. 判斷
if(number != 2){
condition2.await();
}

//2. 打印
for (int i = 1; i <= 2; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
}

//3. 喚醒
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void loopC(int totalLoop){
lock.lock();

try {
//1. 判斷
if(number != 3){
condition3.await();
}

//2. 打印
for (int i = 1; i <= 3; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
}

//3. 喚醒
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

}

輸出結果

A   1   1
B 1 1
B 2 1
C 1 1
C 2 1
C 3 1
-----------------------------------

A 1 2
B 1 2
B 2 2
C 1 2
C 2 2
C 3 2
-----------------------------------

A 1 3
B 1 3
B 2 3
C 1 3
C 2 3
C 3 3
-----------------------------------

A 1 4
B 1 4
B 2 4
C 1 4
C 2 4
C 3 4
-----------------------------------

A 1 5
B 1 5
B 2 5
C 1 5
C 2 5
C 3 5
-----------------------------------

小結:從代碼中可以看出,相比於Object的wait和notify方法,Condition的await和signal方法更具有靈活性。



本人才疏學淺,若有錯誤,還請指出
謝謝!


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com