lock鎖的原理(AQS算法) - 草稿 - 草稿

解決多線程的并發(fā)安全問(wèn)題,Java無(wú)非就是加鎖,具體就是2個(gè)方法。

1.Synchronized(java自帶的關(guān)鍵字)

2.lock可重入鎖(可重入鎖這個(gè)包java.util.concurrent.locks底下有2個(gè)接口,分別對(duì)應(yīng)兩個(gè)實(shí)現(xiàn)類)

? ? ? a.lock接口,實(shí)現(xiàn)類為:ReentrantLock類,可重入鎖。

? ? ? b.readwirtelock接口,實(shí)現(xiàn)類為ReentrantReadWriteLock讀寫鎖。

有就是說(shuō)有三種:

1、synchronized是互斥鎖。

2、ReentrantLock顧名思義是:可重入鎖。

3、ReentrantReadWirteLock是:讀寫鎖。

讀寫鎖的特點(diǎn):
a)多個(gè)線程可以同時(shí)進(jìn)行讀。
b)寫線程必須互斥(只允許一個(gè)線程寫,也不允許讀和寫同時(shí)進(jìn)行)
c)寫線程優(yōu)先于讀線程。(一旦有寫線程,則后續(xù)讀線程必須等待,喚醒時(shí)優(yōu)先考慮寫線程。)

總結(jié)來(lái)說(shuō):Lock和Synchronized有以下幾點(diǎn)不同:

1)Lock是一個(gè)接口,而Synchronized是Java中的關(guān)鍵字,Synchronized是內(nèi)置的語(yǔ)言實(shí)現(xiàn)。

2)當(dāng)Synchronized塊執(zhí)行結(jié)束時(shí),會(huì)自動(dòng)釋放鎖,而lock一般需要我們手動(dòng)在finally塊中釋放。synchronized在發(fā)生異常時(shí),會(huì)自動(dòng)釋放線程占有的鎖,因此不會(huì)導(dǎo)致死鎖現(xiàn)象發(fā)生;而lock在發(fā)生異常時(shí),如果沒(méi)有主動(dòng)通過(guò)unlock()去釋放鎖,則很可能造成死鎖現(xiàn)象,因此使用lock時(shí)需要在finally塊中釋放鎖。

3)lock等待鎖過(guò)程中可以用interrupt來(lái)終斷等待,而synchronized只能等待鎖的釋放,不能響應(yīng)鎖的中斷。

4)lock可以通過(guò)trylock來(lái)知道線程有沒(méi)有獲取到鎖,而synchronized不能知道。

5)當(dāng)synchronized執(zhí)行時(shí),只能使用非公平鎖,無(wú)法實(shí)現(xiàn)公平鎖。而lock可以通過(guò)new ReentrantLock(true) 來(lái)設(shè)置為公平鎖,從而在某些場(chǎng)景下提高效率。

6)lock可以提高多個(gè)線程進(jìn)行讀寫操作的效率。(通過(guò)readwirtelock實(shí)現(xiàn)讀寫分離)

7)synchronized鎖類型:可重入、不可中斷、非公平。而lock鎖類型:可重入、可中斷、可公平。

在性能上來(lái)說(shuō),如果資源競(jìng)爭(zhēng)不激烈,兩者的性能是查不多的,而當(dāng)競(jìng)爭(zhēng)資源非常激烈時(shí),此時(shí)lock的性能要遠(yuǎn)遠(yuǎn)優(yōu)于synchronized。

首先來(lái)看一下synchronized的原理:

1、synchronized

把代碼塊聲明為synchronized,有2個(gè)重要的后果,通常是指該代碼具有原子性和可見(jiàn)性。實(shí)現(xiàn)原子性的算法為CAS

(1)原子性

原子性意味著某個(gè)時(shí)刻 ,只有一個(gè)線程能夠執(zhí)行一段代碼,這段代碼通過(guò)一個(gè)monitor object保護(hù)。從而防止多個(gè)線程在更新共享狀態(tài)時(shí)相互沖突。

(2)可見(jiàn)性

可見(jiàn)性更為微妙,它要對(duì)付內(nèi)存緩存和編譯器優(yōu)化的各種反常行為。啥是可見(jiàn)性呢?

答:它必須確保釋放鎖之前對(duì)共享數(shù)據(jù)做出的更改對(duì)隨后獲得該鎖的另一個(gè)線程是可見(jiàn)的。
作用:如果沒(méi)有同步機(jī)制提供的這種可見(jiàn)性保證,線程看到的共享變量可能是修改前的值或不一致的值,這將引發(fā)許多嚴(yán)重問(wèn)題。一般來(lái)說(shuō),線程以某種不必讓其他線程立即可以看到的方式,不受緩存變量值的約束,但是開(kāi)發(fā)人員如果使用了同步,那么運(yùn)行庫(kù)將確保某一線程對(duì)變量所做的更新先于對(duì)現(xiàn)有synchronized塊所進(jìn)行的更新。當(dāng)進(jìn)入由同一監(jiān)控器(lock)保護(hù)的另一個(gè)synchronized塊時(shí),將立刻可以看到這些對(duì)變量所做的更新。類似的規(guī)則也存在于volatile變量上。
volatile只保證可見(jiàn)性,不保證原子性!

(3)synchronized的限制:

1.當(dāng)線程嘗試獲取鎖的時(shí)候,如果獲取不到鎖會(huì)一直阻塞,他無(wú)法中斷一個(gè)正在等侯獲得鎖的線程;

2.如果獲取鎖的線程進(jìn)入休眠或者阻塞,除非當(dāng)前線程異常,否則其他線程嘗試獲取鎖必須一直等待,也無(wú)法通過(guò)投票得到鎖,如果不想等下去,也就沒(méi)法得到鎖。

2、ReentrantLock(可重入鎖)

可重入的意思是某一個(gè)線程是否可多次獲得一個(gè)鎖,在繼承的情況下,如果不是可重入的,那就形成死鎖了,比如遞歸調(diào)用自己的時(shí)候;如果不能可重入,每次都獲取鎖不合適,比如synchronized就是可重入的,ReentrantLock也是可重入的。

2.1、為什么要可重入

如果線程A繼續(xù)再次獲取這個(gè)鎖呢?比如一個(gè)方法是synchronized的,遞歸調(diào)用自己,那么第一次就已經(jīng)獲得了鎖,在第二次調(diào)用的時(shí)候還能進(jìn)入嗎?直觀上當(dāng)然需要能進(jìn)入,這就是可重入,可重入鎖也叫遞歸鎖,不然就死鎖了。

它實(shí)現(xiàn)的方式是:

為每個(gè)鎖關(guān)聯(lián)一個(gè)獲取計(jì)數(shù)器和一個(gè)所有者線程,當(dāng)計(jì)數(shù)器為0時(shí),這個(gè)鎖就沒(méi)有被任何線程持有,當(dāng)線程請(qǐng)求一個(gè)未被持有的鎖時(shí),JVM會(huì)記下鎖的持有者,并且將獲取計(jì)數(shù)值置為1,如果同一個(gè)線程再次獲取這個(gè)鎖,計(jì)數(shù)值將遞增,退出一次同步代碼塊,計(jì)數(shù)值遞減,當(dāng)計(jì)數(shù)值為0時(shí),這個(gè)鎖就被釋放。ReentrantLock里面有實(shí)現(xiàn)。
其實(shí)也有不可重入鎖:linux下的pthread_mutex_t鎖是默認(rèn)非遞歸的。jdk里面沒(méi)有默認(rèn)的實(shí)現(xiàn)類。
java.util.concurrent.lock中的Lock框架是鎖 定的一個(gè)抽象,lock彌補(bǔ)了synchronized的局限,提供了更加細(xì)粒度的加鎖功能。
ReentrantLock是唯一實(shí)現(xiàn)了Lock接口的實(shí)現(xiàn)類,它擁有與syncjronized相同的并發(fā)性和內(nèi)存語(yǔ)義。但是添加了類似鎖投票、定時(shí)鎖等候和可中斷鎖等候的一些特性。此外,他還提供了在激烈競(jìng)爭(zhēng)下更佳的性能。
使用synchronized修飾的方法或者語(yǔ)句塊在代碼執(zhí)行完成后會(huì)自動(dòng)釋放鎖。而lock需要我們手動(dòng)釋放鎖,所以為了保證鎖最終被釋放,需要把互斥區(qū)放在try內(nèi),釋放鎖放在finally內(nèi)。

Lock接口API如下:

public interface Lock{? ? ? ? ? ?
void lock();?
void lockInterruptibly();
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();

lock()、lockInterruptibly()、trylock()、和trylock(long time,TimeUnit unit)是原來(lái)獲取鎖的。

unlock()是用來(lái)釋放鎖的。

在lock中聲明了4個(gè)方法來(lái)獲取鎖,那么這4個(gè)方法有什么區(qū)別嗎?

lock()方法是平時(shí)使用的最多的一個(gè)方法,就是用來(lái)獲取鎖,如果鎖已被其他線程獲取,則進(jìn)行等待。因?yàn)閘ock鎖是不會(huì)自動(dòng)釋放的,所以一般使用lock()方法時(shí)會(huì)在try塊中進(jìn)行,并且將鎖的釋放放在finally塊中,來(lái)保證鎖一定會(huì)被釋放,防止死鎖的發(fā)生。

tryLock()方法是有返回值的,他表示用來(lái)嘗試獲取鎖,如果獲取成功,則返回true,若獲取失敗則返回false(表示鎖已被其他線程獲?。?。也就是說(shuō)這個(gè)方法無(wú)論獲取成功與失敗都會(huì)返回結(jié)果,并不會(huì)在獲取不到鎖的時(shí)候一直在等待。

tryLock(long time,TimeUtil util)方法也是和tryLock方法類似的,只不過(guò)這個(gè)方法的區(qū)別是它在拿不到鎖的時(shí)候是會(huì)等待一定的時(shí)間的,不會(huì)直接返回false,在等待時(shí)間結(jié)束后,如還未拿到鎖,則返回false。如果在一開(kāi)始或者等待期內(nèi)拿到鎖,則返回true。

lockInterruptibly()方法比較特殊,當(dāng)通過(guò)這個(gè)方法來(lái)獲取鎖時(shí),若線程處在等待狀態(tài),則這個(gè)線程可以響應(yīng)中斷,也就是說(shuō)中斷線程的等待狀態(tài)。假如說(shuō)有2個(gè)線程A和B申請(qǐng)同一個(gè)鎖,當(dāng)A線程得到鎖之后,則B線程就會(huì)進(jìn)入等待狀態(tài),此時(shí)我們可以通過(guò)threadB.interrupt()方法來(lái)終斷線程B的等待過(guò)程。

注意:當(dāng)一個(gè)線程獲得鎖之后,是不會(huì)被interrupt()方法中斷的。單獨(dú)調(diào)用interrupt()方法不能中斷正在運(yùn)行過(guò)程中的線程,只能中斷阻塞過(guò)程中的線程。
因此當(dāng)通過(guò)lockinterruptibly()方法來(lái)獲取某個(gè)鎖的時(shí)候,如果不能獲取到,只有進(jìn)行等待的情況下,是可以響應(yīng)中斷的。
而用synchronized修飾的話,當(dāng)一個(gè)線程處于等待某個(gè)鎖的狀態(tài),是無(wú)法被中斷的,只有一直等待下去。

2 AQS

abstractqueuedsynchronized簡(jiǎn)稱aqs。是一個(gè)用于構(gòu)建鎖和同步容器的框架。事實(shí)上concurrent包內(nèi)許多類都是基于aqs構(gòu)建。例如ReentrantLock,Semaphore,CountDownlatch,ReentrantReadWirteLock,FutureTask等。aqs解決了在實(shí)現(xiàn)同步容器時(shí)設(shè)計(jì)的大量細(xì)節(jié)問(wèn)題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容