整篇文章郊霎,我想由淺到深開始寫沼头。剛開始可能會(huì)有一些比較基礎(chǔ)的內(nèi)容。
一、通過一個(gè)典型的并發(fā)問題进倍,了解鎖到底有什么用
開10個(gè)線程土至,對(duì)count變量進(jìn)行++操作,每個(gè)線程執(zhí)行50次++操作猾昆。
10個(gè)線程陶因,每個(gè)線程加50次,理論上垂蜗,應(yīng)該是500;
但是烘苹,讓我們看下實(shí)際的運(yùn)行結(jié)果螟加。
第一次運(yùn)行結(jié)果:
第二次運(yùn)行結(jié)果:
不是500也就算了然爆,每次運(yùn)行的結(jié)果還不一樣曾雕。很顯然剖张,并發(fā)了搔弄。然后顾犹,我們加上synchronized關(guān)鍵字試試炫刷。
結(jié)果如下:
執(zhí)行結(jié)果正確了绍申。但是极阅,我們也發(fā)現(xiàn)增加synchronized后书在,比增加之前執(zhí)行時(shí)間長(zhǎng)了很多拆又。
二栈源、鎖消除和鎖粗化
不難發(fā)現(xiàn) 甚垦, 是因?yàn)槲覀儗?duì)Thread.currentThread().sleep(100);也加了鎖涣雕,導(dǎo)致運(yùn)行變慢迄埃。
然后我們將synchronized的位置改一下侄非。
運(yùn)行結(jié)果如下:
運(yùn)行結(jié)果正確,但是運(yùn)行時(shí)間快了很多福澡。所以革砸,寫代碼的時(shí)候业岁,一定要注意笔时,不該鎖的代碼,不要鎖扒怖。我們通常也把這種原則叫做鎖消除业稼。
但是俯邓!
如果要是我們頻繁的lock和unlock稽鞭,同樣會(huì)導(dǎo)致大量的開銷朦蕴。這個(gè)時(shí)候,我們需要將多個(gè)連續(xù)的鎖擴(kuò)展成一個(gè)范圍更大的鎖赴恨。這個(gè)叫做鎖粗化嘱支。
有的同學(xué)可能會(huì)說了,我粗也不好扔枫,細(xì)也不好倚舀,你想讓我一粗一細(xì)一粗一細(xì)么!
好吧痕貌,這個(gè)就看自己怎么去衡量了…
三舵稠、ReentrantLock類
Java里除了synchronized關(guān)鍵字外室琢,還有很多其他的方式也可以避免并發(fā)問題盈滴。
比如典型的ReentrantLock巢钓,又叫做可重入鎖竿报。
ReentrantLock是繼承自Lock接口。
除了ReentrantLock外芽世,還有ReentrantReadWriteLock
我們可以發(fā)現(xiàn),ReentrantReadWriteLock并沒有直接實(shí)現(xiàn)Lock接口旺矾。
但是夺克,ReentrantReadWriteLock有兩個(gè)方法柬帕,可以獲取ReadLock和WriteLock陷寝,而ReadLock和WriteLock實(shí)現(xiàn)了Lock接口凤跑。ReentrantReadWriteLock的具體使用和實(shí)現(xiàn)我們將在后面的文章中研究。
本文里叛复,我們先了解下ReentrantLock饶火。
首先鹏控, 我們來創(chuàng)建一個(gè)可重入鎖ReentrantLock,先lock肤寝,然后在finally里unlock当辐。
我們來看下執(zhí)行效果:
什么鬼?明明鎖起來了鲤看,怎么結(jié)果不是500缘揪!
檢查下代碼, 發(fā)現(xiàn)ReentrantLock reentrantLock = new ReentrantLock();這行代碼居然寫在run()方法里面。每次執(zhí)行都會(huì)創(chuàng)建一把新的鎖,當(dāng)然沒用......。那應(yīng)該怎么做呢?
肯定是當(dāng)成成員變量抖韩。
改好之后,再看下效果:
所以庞呕,一定記得要保證:需要同步的代碼塊拿到的是同一把鎖。用吐槽大會(huì)池子的話說:知識(shí)點(diǎn)啊有木有蔚鸥。
四乾巧、什么是可重入鎖社裆?怎么證明synchronized和ReentrantLock都是可重入的榄攀?
前面一直在說ReentrantLock贞瞒,字面翻譯過來掰盘,也就是可重入鎖叨橱,但是一直不明白可重入鎖到底是個(gè)什么鬼?
其實(shí),可重入鎖是一個(gè)概念征椒,并不就僅僅指ReentrantLock,雖然它翻譯過來就是這個(gè)意思瓢省。
可重入鎖辩块,也叫做遞歸鎖,指的是同一線程外層函數(shù)獲得鎖之后 四啰,內(nèi)層遞歸函數(shù)仍然有獲取該鎖的代碼欧瘪,但不受影響佛掖。
通俗點(diǎn)說就是在同一個(gè)線程里調(diào)lock()之后拴魄,哪怕不釋放跛溉,然后再調(diào)一次lock()方法关贵,又可以拿到鎖√考簦可重入鎖主要是為了解決遞歸調(diào)用產(chǎn)生的死鎖問題练链。其實(shí)synchronized也是可重入鎖的一種,因?yàn)閷懥藄ynchronized關(guān)鍵字之后奴拦,遞歸調(diào)用可以正常執(zhí)行兑宇。
為了加深理解,我們自己寫一個(gè)不可重入鎖粱坤,來對(duì)比一下可重入鎖隶糕。
我們自己寫的鎖名字叫MyLock,一樣也繼承Lock接口站玄。
然后我們用MyLock來鎖一下試試:
執(zhí)行結(jié)果如下:
結(jié)果正確枚驻!說明我們實(shí)現(xiàn)了鎖的基本功能。
然后我們來實(shí)驗(yàn)一下需要可重入的場(chǎng)景試試株旷。
我們?cè)趓un方法里再登,執(zhí)行兩次count++操作,對(duì)每個(gè)count++執(zhí)行一次加鎖操作晾剖,但是不解鎖锉矢。
代碼如下:
執(zhí)行結(jié)果如下:
可以看出,用我們自己寫的MyLock鎖齿尽,執(zhí)行了一次沽损,程序就死鎖了。
然后我們把lock換成ReentrantLock再來一次循头。
private static?Lock?lock?=?new?ReentrantLock();
再次執(zhí)行绵估,結(jié)果如下:
跟預(yù)期結(jié)果一樣。
然后我們?cè)俑某蒘ynchronized試試卡骂。
結(jié)果同上国裳。
通過這個(gè)實(shí)驗(yàn),我們可以證明全跨,ReentrantLock和synchronized確實(shí)是可重入鎖缝左。
五、synchronized和ReentrantLock的比較
那么問題來了,既然synchronized和ReentrantLock都是可重入鎖渺杉,synchronized那么方便蛇数,還需要ReentrantLock干啥?難道蛋疼少办?
我們可以看下ReentrantLock實(shí)現(xiàn)lock的方式。
然后Sync是繼承自傳說中的AbstractQueuedSynchronizer(AQS)诵原。
另外在ReentrantLock內(nèi)部還定義了另外兩個(gè)類英妓,分別是FairSync和NonFairSync,這兩個(gè)類就是分別對(duì)應(yīng)的鎖公平分配和不公平分配的兩個(gè)實(shí)現(xiàn)绍赛,它們都繼承自Sync(類圖已經(jīng)清晰的描述出來了繼承結(jié)構(gòu))蔓纠。有關(guān)鎖的分配和釋放邏輯都是封裝在了AQS里面。
而Synchronized實(shí)現(xiàn)的同步和上面提到的AQS的方式是不同的吗蚌,AQS實(shí)現(xiàn)了一套自己的算法來實(shí)現(xiàn)共享資源的合理控制(具體算法實(shí)現(xiàn)腿倚,下文分析),而Synchronized實(shí)現(xiàn)的同步控制是基于java內(nèi)部的對(duì)象鎖的蚯妇。
那什么是java內(nèi)部的對(duì)象鎖呢敷燎?
Java內(nèi)部對(duì)象鎖:JVM中每個(gè)對(duì)象和類實(shí)際上都與一把鎖與之相關(guān)聯(lián),對(duì)于對(duì)象來說箩言,監(jiān)視的是這個(gè)對(duì)象變量硬贯,對(duì)于類來說,監(jiān)視的是類變量陨收。當(dāng)虛擬機(jī)裝載類時(shí)饭豹,會(huì)創(chuàng)建一個(gè)Class類的實(shí)例,鎖住的實(shí)際上是這個(gè)類對(duì)應(yīng)的Class累的實(shí)例务漩。對(duì)象鎖是可重入的拄衰,也就是說一個(gè)對(duì)象或者類上的鎖是可以累加的。
然后網(wǎng)上也有一些ReentrantLock和synchronized的性能比較饵骨。
http://blog.csdn.net/lantian0802/article/details/8948696
各種數(shù)據(jù)都顯示翘悉,ReentrantLock無論哪方面都比synchronized好。而且支持更多的特性居触,比如時(shí)間鎖等候镐确、可中斷鎖等候、無塊結(jié)構(gòu)鎖饼煞、多個(gè)條件變量或者鎖投票源葫。
但是!是的砖瞧,我們來但是了息堂!
很多大神還是建議能用synchronized開發(fā)的時(shí)候,盡量別用ReentrantLock,除非能證明synchronized已經(jīng)不適合所在場(chǎng)景荣堰。因?yàn)榇参矗蠖鄶?shù)synchronized塊幾乎從來沒有出現(xiàn)過爭(zhēng)用,而ReentrantLock比synchronized優(yōu)秀是體現(xiàn)在出現(xiàn)高爭(zhēng)用的場(chǎng)景振坚。但是一旦忘記寫unlock了薇搁,那就是死鎖!
下一篇渡八,我們將具體講解java內(nèi)部的對(duì)象鎖啃洋,包括偏向鎖、輕量級(jí)鎖屎鳍、重量級(jí)鎖和自旋鎖宏娄。