1. 程式人生 > >淺談--Lock鎖 與 Condition

淺談--Lock鎖 與 Condition

一 Lock鎖簡介

1.用處:

Lock鎖與synchronized一樣,都是可以用來控制同步訪問的。

2.有了synchronized,為什麼還要Lock鎖呢?

那就要談到synchronized的缺點,主要是三個方面。

A 有時候用synchronized修飾的程式碼,訪問它需要很長時間,下一個要訪問同一程式碼塊的執行緒就要等待阻塞很長的時間。如果我想要下一個執行緒在等待一段時間後,如果還沒有得到鎖的話,就放棄等待,這就可以使用Lock鎖,來設定等待時間。

B synchronized 是互斥鎖,同一時間只能有一個執行緒可以訪問被它修飾的程式碼塊。而Lock鎖可以實現互斥鎖,也可以實現共享鎖(同一時間支援多條執行緒訪問)。

C 有些情況下,獲取與釋放鎖的情況比較複雜。比如:用於遍歷併發訪問的資料結構的一些演算法需要使用“手動”或“鏈鎖定”:您獲取節點A的鎖定,然後獲取節點B,然後釋放A並獲取C,然後釋放B並獲得D等。在這種場景中synchronized關鍵字就不那麼容易實現了,使用Lock介面容易很多

3.Lock鎖的缺點:

     相比於synchronized,Lock需要手動的獲取鎖與釋放鎖。

4.Lock鎖的高階特性:

A    嘗試非阻塞地獲取鎖       當前執行緒嘗試獲取鎖,如果這一時刻鎖沒有被其他執行緒獲取到,則成功獲取並持有鎖,如果當前時刻,鎖被其它執行緒佔有,則直接返回fasle;

B    能被中斷地獲取鎖   在鎖的獲取過程中可以響應中斷。呼叫 lockInterruptibly() 方法,當通過這個方法去獲取鎖時,如果執行緒正在等待獲取鎖,則這個執行緒能夠響應中斷,即中斷執行緒的等待狀態。也就使說,當兩個執行緒同時通過 lock.lockInterruptibly()想獲取某個鎖時,假若此時執行緒A獲取到了鎖,而執行緒B只有在等待,那麼對執行緒B呼叫 threadB.interrupt()方法能夠中斷執行緒B的等待過程。

注意,當一個執行緒獲取了鎖之後,是不會被interrupt()方法中斷的。因為本身在前面的文章中講過單獨呼叫interrupt()方法不能中斷正在執行過程中的執行緒,只能中斷阻塞過程中的執行緒。

因此當通過lockInterruptibly()方法獲取某個鎖時,如果不能獲取到,只有進行等待的情況下,是可以響應中斷的。

 而用synchronized修飾的話,當一個執行緒處於等待某個鎖的狀態,是無法被中斷的,只有一直等待下去。

C    超時獲取鎖         在指定的截止時間之前獲取鎖, 超過截止時間後仍舊無法獲取則返回

 

二  ReentrantLock

ReentrantLock作為Lock介面的子類,可以實現Lock介面的所有特性。內部有兩個同步器實現類,一個為公平鎖,另一個為非公平鎖。

1.特性:

ReentrantLock的特性就是支援重進入,即任何執行緒在獲取到鎖之後能夠再次獲取該鎖而不會發生堵塞,注意是兩次獲取的是同一個鎖。
ReentrantLock還支援定義公平鎖與非公平鎖,關於同步器與公平鎖,可以看一下 https://mp.csdn.net/postedit/85238441

2.使用API

ReentrantLock lock = new ReentrantLock(true) 定義是否為公平鎖。
lock.tryLock(); 非阻塞 的獲取鎖。
lock.lockInterruptibly();可中斷的獲取鎖

tryLock(long timeout, TimeUnit unit); 設定時間的獲取鎖。

lock。unlock(); 釋放鎖。

 

三  ReentrantReadWriteLock

ReentrantReadWriteLock是讀寫鎖,把對一個資源的鎖分為讀鎖與寫鎖。

1.特性

ReentrantReadWriteLock內部維護了一對鎖。一個是讀鎖,是共享鎖,同一時刻可以允許多個執行緒同時進行訪問,

    還有一個是寫鎖,是排它鎖,同一時刻只允許一個執行緒訪問。

當寫鎖被獲取到時,後續的讀寫操作都會被阻塞,當寫鎖釋放後,所有的操作繼續執行。

ReentrantReadWriteLock也支援重進入與公平性選擇。

2.API

ReentrantReadWriteLock lock = new ReentrantReadWriteLock(false); 建立公平鎖與非公平鎖。
ReentrantReadWriteLock.ReadLock readLock = lock.readLock(); 獲取讀鎖。
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); 獲取寫鎖。

其他的API實現Lock 的高階特性,如非阻塞的獲取鎖,可中斷的獲取鎖。

3.鎖降級

當前執行緒遵循先獲取寫鎖、獲取讀鎖、釋放寫鎖 、釋放讀鎖的過程,稱為鎖降級

目的:為了保證資料的可見性。如果當前執行緒不獲取讀鎖,而是直接釋放寫鎖。假設此時有另一執行緒T獲取寫鎖,並修改了資料。則當前執行緒無法感知到執行緒T的資料更新。如果遵循鎖降級的步驟,執行緒T將會被阻塞,直到當前執行緒使用資料並釋放讀鎖之後,執行緒T才能獲取寫鎖進行資料更新。

 

四  Condition

Condition作為介面使用的是ConditionObject物件,ConditionObject是AQS的內部類,每個Condition物件都有一個FIFO等待佇列,用於儲存處於等待狀態的執行緒。

1.作用:

        可以實現比  wait()和notify/notifyAll()方法更高階的    等待/通知機制

2 wait()和notify/notifyAll()的介紹

   在使用notify/notifyAll()方法進行通知時,被通知的執行緒是有JVM選擇的,

   執行notifyAll()方法的話就會通知所有處於等待狀態的執行緒

3.Condition

 一個Lock物件中可以建立多個Condition例項,一個Condition可以註冊多個執行緒,從而可以有選擇性的進行執行緒通知

而synchronized關鍵字就相當於整個Lock物件中只有一個Condition例項,所有的執行緒都註冊在它一個身上。

Condition例項的signalAll()方法 只會喚醒註冊在該Condition例項中的所有等待執行緒

4.用法

Condition condition1 = lock.newCondition();     建立Condition
condition1.await();            使當前執行緒釋放鎖,構造成節點,加入等待佇列尾部,進入等待狀態。
condition1.signal();          喚醒註冊在此Condition上等待時間最長的執行緒。即喚醒在等待佇列首節點的執行緒
condition1.signalAll();      喚醒註冊在此Condition上所有等待的執行緒。

5.注意

A     必須在condition.await()方法呼叫之前呼叫lock.lock()程式碼獲得鎖,不然會報錯。

B    在使用wait/notify實現等待通知機制的時候我們知道必須執行完notify()方法所在的synchronized程式碼塊後才釋放鎖。在這裡也差不多,必須執行完signal所在的try語句塊之後才釋放鎖,condition.await()後的語句才能被執行。

6.關於同步佇列與等待佇列的問題

Object模型只有一條同步佇列與一條等待佇列,所有處於等待的執行緒都位於等待佇列中。呼叫await()方法時,由於此時已經在同步佇列中,並獲取了鎖,使當前執行緒釋放鎖,構造成節點,加入等待佇列尾部,進入等待狀態。當從等待佇列中將執行緒喚醒時,是隨機喚醒的(synchronized 與 notify組合),使等待佇列中被喚醒的執行緒,加入到同步佇列中,進入鎖的獲取競爭中。成功獲取了鎖的執行緒,才從await()方法後開始執行。

Lock模型有一條同步佇列與多條等待佇列,即上文關於Condition與synchronized的分析。加入等待佇列與喚醒執行緒與Object模型相同,只是當喚醒執行緒時,不是隨機通知的,而是喚醒在等待佇列中等待時間最長的執行緒,即等待佇列中的首節點。

 

歡迎點贊與關注小編,小編水平有限,錯誤的地方請指出來。

歡迎評論與討論,小編會盡力解答。