參考博文:https://blog.csdn.net/u010842515/article/details/67634813
一.Java多線程實(shí)現(xiàn)加鎖的兩種方式:
- synchronized關(guān)鍵字(實(shí)際上是悲觀鎖的思想)
- Java.util.concurrent包中的lock接口和ReentrantLock實(shí)現(xiàn)類
二.synchronized關(guān)鍵字加鎖的缺陷:
一段代碼塊被synchronized修飾了,一個(gè)線程獲取了對(duì)應(yīng)的鎖,并執(zhí)行該代碼塊是,其他線程只能一支等待獲取鎖的線程釋放鎖。但這里獲取鎖的線程釋放鎖,只有兩種情況:
1.獲取鎖的線程執(zhí)行完了代碼塊,然后釋放對(duì)鎖的占用。
2.線程執(zhí)行發(fā)生異常,JVM會(huì)自動(dòng)讓線程釋放鎖。
因此就需要有一種機(jī)制可以不讓等待的線程一直無期限地等待下去(比如只等待一定的時(shí)間或者能夠響應(yīng)中斷),通過Lock就可以辦到。
再舉個(gè)例子:當(dāng)有多個(gè)線程讀寫文件時(shí),讀操作和寫操作會(huì)發(fā)生沖突現(xiàn)象,寫操作和寫操作會(huì)發(fā)生沖突現(xiàn)象,但是讀操作和讀操作不會(huì)發(fā)生沖突現(xiàn)象。
但是采用synchronized關(guān)鍵字來實(shí)現(xiàn)同步的話,就會(huì)導(dǎo)致一個(gè)問題:
如果多個(gè)線程都只是進(jìn)行讀操作,所以當(dāng)一個(gè)線程在進(jìn)行讀操作時(shí),其他線程只能等待無法進(jìn)行讀操作。
因此就需要一種機(jī)制來使得多個(gè)線程都只是進(jìn)行讀操作時(shí),線程之間不會(huì)發(fā)生沖突,通過Lock就可以辦到。 另外,通過Lock可以知道線程有沒有成功獲取到鎖。這個(gè)是synchronized無法辦到的。
對(duì)比:
1)Lock不是Java語言內(nèi)置的,synchronized是Java語言的關(guān)鍵字,因此是內(nèi)置特性。Lock是一個(gè)類,通過這個(gè)類可以實(shí)現(xiàn)同步訪問;
2)Lock和synchronized有一點(diǎn)非常大的不同,采用synchronized不需要用戶去手動(dòng)釋放鎖,當(dāng)synchronized方法或者synchronized代碼塊執(zhí)行完之后,系統(tǒng)會(huì)自動(dòng)讓線程釋放對(duì)鎖的占用;而Lock則必須要用戶去手動(dòng)釋放鎖,如果沒有主動(dòng)釋放鎖,就有可能導(dǎo)致出現(xiàn)死鎖現(xiàn)象。
三.Lock和synchronized的選擇:
總結(jié)來說,Lock和synchronized有以下幾點(diǎn)不同:
1)Lock是一個(gè)接口,而synchronized是Java中的關(guān)鍵字,synchronized是內(nèi)置的語言實(shí)現(xiàn);
2)synchronized在發(fā)生異常時(shí),會(huì)自動(dòng)釋放線程占有的鎖,因此不會(huì)導(dǎo)致死鎖現(xiàn)象發(fā)生;而Lock在發(fā)生異常時(shí),如果沒有主動(dòng)通過unLock()去釋放鎖,則很可能造成死鎖現(xiàn)象,因此使用Lock時(shí)需要在finally塊中釋放鎖;
3)Lock可以讓等待鎖的線程響應(yīng)中斷,而synchronized卻不行,使用synchronized時(shí),等待的線程會(huì)一直等待下去,不能夠響應(yīng)中斷;(I/O和Synchronized都能相應(yīng)中斷,即不需要處理interruptionException異常)
4)通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。
5)Lock可以提高多個(gè)線程進(jìn)行讀操作的效率。
在性能上來說,如果競(jìng)爭(zhēng)資源不激烈,兩者的性能是差不多的,而當(dāng)競(jìng)爭(zhēng)資源非常激烈時(shí)(即有大量線程同時(shí)競(jìng)爭(zhēng)),此時(shí)Lock的性能要遠(yuǎn)遠(yuǎn)優(yōu)于synchronized。所以說,在具體使用時(shí)要根據(jù)適當(dāng)情況選擇。
四. 鎖的種類介紹:
1.可重入鎖
如果鎖具備可重入性,則稱作為可重入鎖。像synchronized和ReentrantLock都是可重入鎖,可重入性在我看來實(shí)際上表明了鎖的分配機(jī)制:基于線程的分配,而不是基于方法調(diào)用的分配。舉個(gè)簡(jiǎn)單的例子,當(dāng)一個(gè)線程執(zhí)行到某個(gè)synchronized方法時(shí),比如說method1,而在method1中會(huì)調(diào)用另外一個(gè)synchronized方法method2,此時(shí)線程不必重新去申請(qǐng)鎖,而是可以直接執(zhí)行方法method2。

上述代碼中的兩個(gè)方法method1和method2都用synchronized修飾了,假如某一時(shí)刻,線程A執(zhí)行到了method1,此時(shí)線程A獲取了這個(gè)對(duì)象的鎖,而由于method2也是synchronized方法,假如synchronized不具備可重入性,此時(shí)線程A需要重新申請(qǐng)鎖。但是這就會(huì)造成一個(gè)問題,因?yàn)榫€程A已經(jīng)持有了該對(duì)象的鎖,而又在申請(qǐng)獲取該對(duì)象的鎖,這樣就會(huì)線程A一直等待永遠(yuǎn)不會(huì)獲取到的鎖。
而由于synchronized和Lock都具備可重入性,所以不會(huì)發(fā)生上述現(xiàn)象。
2.可中斷鎖:可以相應(yīng)中斷的鎖。
在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。
如果某一線程A正在執(zhí)行鎖中的代碼,另一線程B正在等待獲取該鎖,可能由于等待時(shí)間過長(zhǎng),線程B不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在別的線程中中斷它,這種就是可中斷鎖。
在前面演示lockInterruptibly()的用法時(shí)已經(jīng)體現(xiàn)了Lock的可中斷性。
3.公平鎖
公平鎖即盡量以請(qǐng)求鎖的順序來獲取鎖。比如同是有多個(gè)線程在等待一個(gè)鎖,當(dāng)這個(gè)鎖被釋放時(shí),等待時(shí)間最久的線程(最先請(qǐng)求的線程)會(huì)獲得該所,這種就是公平鎖。
非公平鎖即無法保證鎖的獲取是按照請(qǐng)求鎖的順序進(jìn)行的。這樣就可能導(dǎo)致某個(gè)或者一些線程永遠(yuǎn)獲取不到鎖。
在Java中,synchronized就是非公平鎖,它無法保證等待的線程獲取鎖的順序。
而對(duì)于ReentrantLock和ReentrantReadWriteLock,它默認(rèn)情況下是非公平鎖,但是可以設(shè)置為公平鎖。
看一下這2個(gè)類的源代碼就清楚了:

在ReentrantLock中定義了2個(gè)靜態(tài)內(nèi)部類,一個(gè)是NotFairSync,一個(gè)是FairSync,分別用來實(shí)現(xiàn)非公平鎖和公平鎖。
我們可以在創(chuàng)建ReentrantLock對(duì)象時(shí),通過以下方式來設(shè)置鎖的公平性:
ReentrantLock lock = new ReentrantLock(true);
如果參數(shù)為true表示為公平鎖,為fasle為非公平鎖。默認(rèn)情況下,如果使用無參構(gòu)造器,則是非公平鎖。
另外在ReentrantLock類中定義了很多方法,比如:
isFair() //判斷鎖是否是公平鎖
isLocked() //判斷鎖是否被任何線程獲取了
isHeldByCurrentThread() //判斷鎖是否被當(dāng)前線程獲取了
hasQueuedThreads() //判斷是否有線程在等待該鎖
在ReentrantReadWriteLock中也有類似的方法,同樣也可以設(shè)置為公平鎖和非公平鎖。不過要記住,ReentrantReadWriteLock并未實(shí)現(xiàn)Lock接口,它實(shí)現(xiàn)的是ReadWriteLock接口。
4.讀寫鎖
讀寫鎖將對(duì)一個(gè)資源(比如文件)的訪問分成了2個(gè)鎖,一個(gè)讀鎖和一個(gè)寫鎖。
正因?yàn)橛辛俗x寫鎖,才使得多個(gè)線程之間的讀操作不會(huì)發(fā)生沖突。
ReadWriteLock就是讀寫鎖,是接口,ReentrantReadWriteLock實(shí)現(xiàn)了這個(gè)接口。
可以通過readLock()獲取讀鎖,通過writeLock()獲取寫鎖。