Java线程安全Lock、ReentrantLock、ReentrantReadWriteLock
轉載請標明出處:http://blog.csdn.net/zhaoyanjun6/article/details/120750932
本文出自【趙彥軍的博客】
Java線程安全StampedLock
Java線程安全Lock、ReentrantLock、ReentrantReadWriteLock
Java線程安全集合總結
Java原子操作Atomic
文章目錄
- 前言
- Lock
- ReentrantLock
- 公平鎖/非公平鎖
- 超時機制
- 可重入鎖
- 讀寫鎖 ReentrantReadWriteLock
- 源碼結構
- 總結
- 示例
前言
java5之后,并發包中新增了Lock接口(以及相關實現類)用來實現鎖的功能,它提供了與synchronized關鍵字類似的同步功能。
既然有了synchronized這種內置的鎖功能,為何要新增Lock接口?先來想象一個場景:手把手的進行鎖獲取和釋放,先獲得鎖A,然后再獲取鎖B,當獲取鎖B后釋放鎖A同時獲取鎖C,當鎖C獲取后,再釋放鎖B同時獲取鎖D,以此類推,這種場景下,synchronized關鍵字就不那么容易實現了,而使用Lock卻顯得容易許多。
Lock
Lock 是一個接口
public interface Lock {void lock();void lockInterruptibly() throws InterruptedException;boolean tryLock();boolean tryLock(long time, TimeUnit unit) throws InterruptedException;void unlock();Condition newCondition(); }ReentrantLock
public class ReentrantLock implements Lock, java.io.Serializable { }使用如下:
public class LockTest {Lock lock = new ReentrantLock();public void run(){//獲取鎖 lock.lock();//doSomething//釋放鎖lock.unlock();} }使用起來,也非常簡單,首先 lock.lock(); 獲取鎖,然后去執行自己的邏輯,最后調用 lock.unlock(); 釋放鎖。
但是這里有個問題,如果我們的邏輯發生異常,那就永遠無法釋放鎖,所以我們優化一下,把釋放鎖的邏輯放在 finally 塊中,如下
public class LockTest {Lock lock = new ReentrantLock();public void run(){//獲取鎖lock.lock();try {//doSomething}catch (Exception e){}finally {//釋放鎖lock.unlock();}} }公平鎖/非公平鎖
-
非公平鎖:如果同時還有另一個線程進來嘗試獲取,那么有可能會讓這個線程搶先獲取;
-
公平鎖:如果同時還有另一個線程進來嘗試獲取,當它發現自己不是在隊首的話,就會排到隊尾,由隊首的線程獲取到鎖。
ReentrantLock 主要利用CAS+AQS隊列來實現。它支持公平鎖和非公平鎖,兩者的實現類似。
- CAS:Compare and Swap,比較并交換。CAS有3個操作數:內存值V、預期值A、要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做。該操作是一個原子操作,被廣泛的應用在Java的底層實現中。在Java中,CAS主要是由sun.misc.Unsafe這個類通過JNI調用CPU底層指令實現
ReentrantLock主要利用CAS+AQS隊列來實現。它支持公平鎖和非公平鎖,兩者的實現類似。
AbstractQueuedSynchronizer 簡稱AQS
我們來看一下 ReentrantLock 構造函數
public ReentrantLock() {sync = new NonfairSync();}/*** Creates an instance of {@code ReentrantLock} with the* given fairness policy.** @param fair {@code true} if this lock should use a fair ordering policy*/public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}構造支持傳入 fair 來指定是否是公平鎖
- FairSync() 公平鎖
- NonfairSync() 非公平鎖
如果使用無參構造函數,則是非公平鎖。
超時機制
在 ReetrantLock的tryLock(long timeout, TimeUnit unit) 提供了超時獲取鎖的功能。它的語義是在指定的時間內如果獲取到鎖就返回true,獲取不到則返回false。這種機制避免了線程無限期的等待鎖釋放。
可重入鎖
-
可重入鎖。可重入鎖是指同一個線程可以多次獲取同一把鎖。ReentrantLock和synchronized都是可重入鎖。
-
可中斷鎖。可中斷鎖是指線程嘗試獲取鎖的過程中,是否可以響應中斷。synchronized是不可中斷鎖,而ReentrantLock則提供了中斷功能。
-
公平鎖與非公平鎖。公平鎖是指多個線程同時嘗試獲取同一把鎖時,獲取鎖的順序按照線程達到的順序,而非公平鎖則允許線程“插隊”。synchronized是非公平鎖,而ReentrantLock的默認實現是非公平鎖,但是也可以設置為公平鎖。
讀寫鎖 ReentrantReadWriteLock
現實中有這樣一種場景:對共享資源有讀和寫的操作,且寫操作沒有讀操作那么頻繁。
在沒有寫操作的時候,多個線程同時讀一個資源沒有任何問題,所以應該允許多個線程同時讀取共享資源;但是如果一個線程想去寫這些共享資源,就不應該允許其他線程對該資源進行讀和寫的操作了。
針對這種場景,JAVA的并發包提供了讀寫鎖 ReentrantReadWriteLock,它表示兩個鎖,一個是讀操作相關的鎖,稱為共享鎖;一個是寫相關的鎖,稱為排他鎖,描述如下:
線程進入讀鎖的前提條件:
-
沒有其他線程的寫鎖,
-
沒有寫請求或者有寫請求,但調用線程和持有鎖的線程是同一個。
線程進入寫鎖的前提條件:
-
沒有其他線程的讀鎖
-
沒有其他線程的寫鎖
而讀寫鎖有以下三個重要的特性:
(1)公平選擇性:支持非公平(默認)和公平的鎖獲取方式,吞吐量還是非公平優于公平。
(2)重進入:讀鎖和寫鎖都支持線程重進入。
(3)鎖降級:遵循獲取寫鎖、獲取讀鎖再釋放寫鎖的次序,寫鎖能夠降級成為讀鎖。
源碼結構
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {/** 讀鎖 */private final ReentrantReadWriteLock.ReadLock readerLock;/** 寫鎖 */private final ReentrantReadWriteLock.WriteLock writerLock;final Sync sync;/** 使用默認(非公平)的排序屬性創建一個新的 ReentrantReadWriteLock */public ReentrantReadWriteLock() {this(false);}/** 使用給定的公平策略創建一個新的 ReentrantReadWriteLock */public ReentrantReadWriteLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();readerLock = new ReadLock(this);writerLock = new WriteLock(this);}/** 返回用于寫入操作的鎖 */public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }/** 返回用于讀取操作的鎖 */public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }abstract static class Sync extends AbstractQueuedSynchronizer {}static final class NonfairSync extends Sync {}static final class FairSync extends Sync {}public static class ReadLock implements Lock, java.io.Serializable {}public static class WriteLock implements Lock, java.io.Serializable {} }1、類的繼承關系
public class ReentrantReadWriteLockimplements ReadWriteLock, java.io.Serializable {}說明:可以看到,ReentrantReadWriteLock實現了ReadWriteLock接口,ReadWriteLock接口定義了獲取讀鎖和寫鎖的規范,具體需要實現類去實現;同時其還實現了Serializable接口,表示可以進行序列化,在源代碼中可以看到ReentrantReadWriteLock實現了自己的序列化邏輯。
2、ReentrantReadWriteLock有五個內部類,五個內部類之間也是相互關聯的。內部類的關系如下圖所示。
說明:如上圖所示,Sync繼承自AQS、NonfairSync繼承自Sync類、FairSync繼承自Sync類(通過構造函數傳入的布爾值決定要構造哪一種Sync實例);ReadLock實現了Lock接口、WriteLock也實現了Lock接口。
總結
-
在線程持有讀鎖的情況下,該線程不能取得寫鎖(因為獲取寫鎖的時候,如果發現當前的讀鎖被占用,就馬上獲取失敗,不管讀鎖是不是被當前線程持有)。
-
在線程持有寫鎖的情況下,該線程可以繼續獲取讀鎖(獲取讀鎖時如果發現寫鎖被占用,只有寫鎖沒有被當前線程占用的情況才會獲取失敗)。
仔細想想,這個設計是合理的:因為當線程獲取讀鎖的時候,可能有其他線程同時也在持有讀鎖,因此不能把獲取讀鎖的線程“升級”為寫鎖;而對于獲得寫鎖的線程,它一定獨占了讀寫鎖,因此可以繼續讓它獲取讀鎖,當它同時獲取了寫鎖和讀鎖后,還可以先釋放寫鎖繼續持有讀鎖,這樣一個寫鎖就“降級”為了讀鎖。
綜上:
一個線程要想同時持有寫鎖和讀鎖,必須先獲取寫鎖再獲取讀鎖;寫鎖可以“降級”為讀鎖;讀鎖不能“升級”為寫鎖。
示例
public class LockTest {ReadWriteLock lock = new ReentrantReadWriteLock();Lock readLock = lock.readLock();Lock writeLock = lock.writeLock();}總結
以上是生活随笔為你收集整理的Java线程安全Lock、ReentrantLock、ReentrantReadWriteLock的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android ConstraintLa
- 下一篇: Android View Binding