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