日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

锁的优化和注意事项

發(fā)布時(shí)間:2024/4/13 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 锁的优化和注意事项 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
鎖的相關(guān)的知識(shí),包括有關(guān)鎖的優(yōu)化,虛擬機(jī)內(nèi)部對(duì)鎖的優(yōu)化,有關(guān)鎖的錯(cuò)誤的使用案例,最后我們會(huì)介紹ThreadLocal,這個(gè)類(lèi)的一些使用,和他內(nèi)部的一些實(shí)現(xiàn)

鎖的優(yōu)化他其實(shí)是希望能夠在多線程并發(fā)的時(shí)候呢,當(dāng)你涉及到有鎖這個(gè)動(dòng)作的時(shí)候,是盡可能的講這個(gè)鎖的性能得到一個(gè)提升,并發(fā)是有阻塞的和非阻塞的,非阻塞的呢可以進(jìn)一步細(xì)分為無(wú)障礙的,無(wú)鎖的,無(wú)等待的,一旦用到鎖這個(gè)東西呢,其實(shí)你就是一個(gè)阻塞的并發(fā),就是我們并發(fā)等級(jí)是比較糟糕的一種,一旦用到了鎖,那你的并發(fā)性能一般是比不上無(wú)鎖的這種方式,也就是無(wú)等待也好,無(wú)障礙也好,他的并發(fā)要比有鎖的要高一點(diǎn),那這里的鎖優(yōu)化是說(shuō),我怎么在這個(gè)阻塞的狀態(tài)之下呢,盡可能的不要讓性能變得太差,事實(shí)上對(duì)于阻塞的寫(xiě)法呢,你再怎么優(yōu)化,也比無(wú)鎖的性能要略低一點(diǎn),因?yàn)楫吘股婕暗骄€程的掛起,但是我們要想辦法,對(duì)性能的影響呢,降到最低,這個(gè)是我們課程的一個(gè)出發(fā)點(diǎn),而并不是說(shuō),他的性能就有一個(gè)質(zhì)的飛躍,包括一些無(wú)鎖的方式,這個(gè)也應(yīng)該是做不到的,那么要進(jìn)行鎖的優(yōu)化呢,我們有幾個(gè)思路,如果我們要使用這個(gè)鎖的話,如果我們使用synchronized關(guān)鍵字,我們使用ReentrantLock的lock方法,如果你是使用ReentrantLock重入鎖的lock,trylcok,我們就不認(rèn)為是鎖,因?yàn)閠rylock他其實(shí)是一種無(wú)鎖的方式,因?yàn)樗⒉粫?huì)在這個(gè)地方把自己掛起,試一下如果拿不到就做其他的事情了,lock就會(huì)把自己給掛起,是一種鎖,有關(guān)鎖我們有這么幾種方式

首先我們來(lái)看一下減少鎖的持有時(shí)間,那我們知道程序當(dāng)中呢,如果說(shuō)我們加了synchronized關(guān)鍵字,在進(jìn)入方法之前呢,會(huì)拿到對(duì)象實(shí)例的鎖,如果這個(gè)方法要做很多的事情,那么在這個(gè)時(shí)候呢,一個(gè)線程進(jìn)來(lái)之后呢,你很有可能會(huì)導(dǎo)致另外一個(gè)線程呢,就進(jìn)不來(lái),就會(huì)等待,優(yōu)化的一個(gè)基本思想呢,我要盡可能的減少其他線程的等待時(shí)間,因此我們要把持有鎖的時(shí)間給縮短,一旦我持有鎖的時(shí)間縮短了,大家進(jìn)入臨界區(qū)的可能性概率就會(huì)降低,就是基于這種思想,對(duì)于像這段代碼,其中有線程安全要求的,可能就只有中間的一段,就是中間的一小段,如果只有中間一小段需要用到鎖,其它的不需要用到鎖之外呢,把這些不需要同步的代碼,也放到同步的區(qū)間當(dāng)中去,就可以把他變成這個(gè)樣子,只同步相關(guān)的代碼,無(wú)關(guān)的代碼就不需要同步,這個(gè)就將所得持有時(shí)間和持有范圍呢,盡可能的縮小,減少?zèng)_突的可能性,這個(gè)是我們要做的一件事情

下面就是減少鎖的粒度,什么叫減少鎖的粒度,很重的對(duì)象,很大的對(duì)象加鎖,什么叫很大的對(duì)象呢,這個(gè)對(duì)象可能同時(shí)被很多對(duì)象訪問(wèn),這個(gè)時(shí)候我們有想法呢,開(kāi)成更少粒度的對(duì)象,大對(duì)象分成小對(duì)象,大對(duì)象分成小對(duì)象的好處呢,增加并行度,降低鎖的競(jìng)爭(zhēng),偏向鎖,輕量級(jí)鎖成功率會(huì)更高,因?yàn)槟銣p少競(jìng)爭(zhēng)之后,偏向鎖和輕量級(jí)鎖成功率才會(huì)提高,至于偏向鎖和輕量級(jí)鎖,這個(gè)是什么東西,在后面會(huì)進(jìn)一步介紹,那么一個(gè)經(jīng)典的例子呢,就是關(guān)于HashMap的同步,我們知道HashMap并不是一個(gè)線程同步的實(shí)現(xiàn),不是一個(gè)線程安全的實(shí)現(xiàn),如果你在多線程下面,HashMap一種比較簡(jiǎn)單的方法呢,在map外面做一層synchronizedMap的封裝,封裝完了之后就變成同步的了,這個(gè)工作是怎么做的呢,它內(nèi)部就是對(duì)Map的get和put方法都做了互斥方法上的同步,在進(jìn)行g(shù)et和put之前呢,我都要把互斥上的鎖,監(jiān)視器給拿到,那這樣做會(huì)有什么問(wèn)題呢,這個(gè)HashMap就是我們說(shuō)的很重的對(duì)象,因?yàn)槔锩嬗泻枚嗪枚鄶?shù)據(jù),當(dāng)所有的線程都進(jìn)來(lái)訪問(wèn)的時(shí)候呢,不管你是讀還是寫(xiě),你都要拿到這個(gè)互斥的對(duì)象,因此你讀會(huì)堵塞寫(xiě),寫(xiě)也會(huì)阻塞讀,同時(shí)當(dāng)你有多個(gè)寫(xiě)多個(gè)讀的時(shí)候呢,線程是一個(gè)一個(gè)進(jìn)來(lái)的,每次HashMap只支持一個(gè)的讀寫(xiě),相對(duì)來(lái)講我們之前介紹過(guò)的ConcurrentHashMap呢,他就是一個(gè)高性能的Hash表,他就做了一個(gè)減少所粒度的操作,之前有說(shuō)過(guò),它是把一整個(gè)HashMap,把它拆成若干個(gè),我印象當(dāng)中是拆成16個(gè)小的Segment,每一個(gè)小的HashMap,這樣每一個(gè)線程在操作的時(shí)候呢,他只會(huì)取操作拆分過(guò)的Segment HashMap,從而增加了并行度,這個(gè)就是減少鎖粒度的一個(gè)思想

這個(gè)就是HashMap操作的說(shuō)明,就是把一個(gè)大的Hash表拆分成小的Hash表,減少鎖粒度之后呢,ConcurrentHashMap就允許若干個(gè)線程同時(shí)進(jìn)入

下面就是鎖分離,也是類(lèi)似的一種思路,如果我對(duì)這個(gè)程序有讀和寫(xiě)的要求,一般的鎖呢,讀讀之間和寫(xiě)寫(xiě)之間也會(huì)進(jìn)行阻塞,那我們要想辦法讓阻塞盡可能的小,這種情況我們就可以使用讀寫(xiě)鎖,讀寫(xiě)鎖的基本思想呢,就是把讀和寫(xiě)進(jìn)行分離,因?yàn)樽x不會(huì)改變數(shù)據(jù),所以讀和讀之間呢,是不需要進(jìn)行同步的,因此讀鎖和讀鎖是可以相互進(jìn)行訪問(wèn)的,因?yàn)楫?dāng)你有若干個(gè)讀的時(shí)候呢,可以一起訪問(wèn),不會(huì)相互阻塞,當(dāng)你如果不使用讀寫(xiě)鎖,同樣是讀的操作呢,如果兩個(gè)線程做兩個(gè)get,get和get之間也是會(huì)做這個(gè)阻塞的,這種情況其實(shí)并不是我們想看到的,很顯然如果我兩個(gè)get再進(jìn)來(lái)的話,他們是不會(huì)相互影響的,因此也沒(méi)有必要去做這個(gè)同步,那么使用這個(gè)讀寫(xiě)鎖呢,他就可以很好地來(lái)解決這個(gè)問(wèn)題,讀跟讀是沒(méi)有關(guān)系的,寫(xiě)可能會(huì)導(dǎo)致數(shù)據(jù)寫(xiě)了一半,出現(xiàn)不一致的情況,所以一旦有寫(xiě)鎖進(jìn)入呢,它是需要做同步,但是因?yàn)槲覀兇蟛糠智闆r之下呢,或者很多應(yīng)用來(lái)說(shuō)呢,讀的場(chǎng)景遠(yuǎn)遠(yuǎn)要大于寫(xiě)的場(chǎng)景,因此一旦使用讀寫(xiě)鎖之后呢,讀多寫(xiě)少的情況之下呢,他就可以很好地提高性能,根據(jù)功能模塊把鎖分為不同的級(jí)別,對(duì)于要求不高的線程呢,可以做一個(gè)并發(fā)的訪問(wèn),從而提高系統(tǒng)的性能,那么之前有說(shuō)過(guò),如果出現(xiàn)讀鎖和讀鎖同時(shí)可以進(jìn)入的情況呢,實(shí)際上就是無(wú)等待的并發(fā),因?yàn)樗芯€程在若干步之內(nèi),有限步之內(nèi)完成他的操作,通過(guò)這種方式將一個(gè)阻塞的并發(fā)呢,無(wú)等待的并發(fā),性能會(huì)好很多

鎖分離的進(jìn)一步延生呢,就是LinkedBlockingQueue,任務(wù)盜竊的時(shí)候呢,當(dāng)一個(gè)線程執(zhí)行自己的任務(wù),和一個(gè)線程偷取別的線程的任務(wù)的時(shí)候,他們?nèi)蝿?wù)隊(duì)列當(dāng)中的數(shù)據(jù)呢,是兩個(gè)不同的端去拿的,一個(gè)是從頭部去拿,另外一個(gè)是從尾部去拿,分離的一個(gè)思想,如果他們都從頭部去拿,可能會(huì)產(chǎn)生沖突,都從尾部去拿也會(huì)產(chǎn)生沖突,所以我一個(gè)從頭部,一個(gè)從尾部,那就沒(méi)有問(wèn)題,LinkedBlockingQueue拿數(shù)據(jù)我們是從頭部去take拿數(shù)據(jù),我put加數(shù)據(jù)的時(shí)候呢,我就加到尾部去,這種情況之下呢,我頭部的操作和尾部的操作呢,其實(shí)是兩個(gè)沒(méi)有關(guān)系的,除非我的元素只有一個(gè),而如果我有若干個(gè)元素的話呢,對(duì)于鏈表來(lái)講,這兩種操作,是不沖突的,因此我們可以給他一個(gè)更高級(jí)別的并發(fā),根據(jù)功能進(jìn)行拆分

我們之前有說(shuō)過(guò),為了保證多線程的有效并發(fā)呢,會(huì)要求每個(gè)線程持有鎖的時(shí)間盡量短,但是有些情況之下,如果說(shuō)我們一個(gè)程序,對(duì)于一個(gè)鎖有不斷地高頻率的請(qǐng)求,你會(huì)有同步,你會(huì)有釋放,你再去請(qǐng)求這個(gè)鎖,當(dāng)你有這個(gè)現(xiàn)象產(chǎn)生的時(shí)候,這個(gè)鎖的同步釋放判斷,也是會(huì)消耗一定的資源的,這樣反而不利于系統(tǒng)的性能優(yōu)化,任何事情都是有個(gè)度的,任何事情他都是有兩面性,有些場(chǎng)景之下呢,我們反而是把很多次鎖的請(qǐng)求把它合并成一個(gè)請(qǐng)求,這樣使得我在一個(gè)請(qǐng)求當(dāng)中呢,我就拿到了這個(gè)鎖

比如我們?cè)谶@個(gè)方法當(dāng)中,我們?nèi)プ鲞@個(gè)同步,然后我們?cè)谶@里做了一個(gè)不需要同步的操作,這個(gè)操作可以很快的完成,我們?cè)龠M(jìn)行同步,你像這種情況呢,就應(yīng)該合并成這樣,我們兩次同步合并成一次同步,這個(gè)是有前提的,前提就是說(shuō),我中間不需要同步的代碼,應(yīng)該是可以很快的完成的,如果我中間不需要同步的代碼,也要花很長(zhǎng)的時(shí)間,就會(huì)被這個(gè)同步塊無(wú)緣無(wú)故的調(diào)用很長(zhǎng)的時(shí)間,這個(gè)絕對(duì)是不合理的,因此這個(gè)前提是很快可以完成的,你可以把多個(gè)同步的操作呢,變成一個(gè)

不停的去請(qǐng)求這個(gè)數(shù),看起來(lái)沒(méi)有什么問(wèn)題,實(shí)際上這個(gè)鎖會(huì)被請(qǐng)求很多次,JDK內(nèi)部會(huì)對(duì)這個(gè)東西,會(huì)對(duì)這個(gè)請(qǐng)求做一些優(yōu)化,但是你還不如寫(xiě)成這樣,因?yàn)槲疫@里循環(huán)呢,循環(huán)好多次,好多次不停的去請(qǐng)求這鎖,其實(shí)對(duì)你是沒(méi)有任何好處的,這種情況之下呢,你還不如寫(xiě)成這樣,我們?cè)谘h(huán)外側(cè),去申請(qǐng)一個(gè)鎖,然后我們等待這個(gè)循環(huán)完成,這樣我從頭到尾只需要請(qǐng)求一次鎖,因?yàn)槟忝恳淮窝h(huán),其實(shí)意義并不是特別大,除非你是有一些特別的情況,你可能是有些特別的情況,循環(huán)的時(shí)間太久,那我也等不及,也要給其他線程工作的機(jī)會(huì),那你還只能寫(xiě)成這樣子,如果沒(méi)有這種特殊需求的情況,因?yàn)樗粚?duì)鎖進(jìn)行一次請(qǐng)求,而這種情況會(huì)進(jìn)行好多次請(qǐng)求

鎖消除是發(fā)生在編譯器級(jí)別的事情,有些時(shí)候,完全不可能加鎖,為什么還要寫(xiě)個(gè)鎖呢,我這個(gè)鎖代碼不寫(xiě),不就可以了嗎,但是有些時(shí)候鎖并不是你的代碼產(chǎn)生的,JDK內(nèi)部就有這個(gè)問(wèn)題,比如使用JDK當(dāng)中的一些類(lèi),Vector,StringBuffer,那么StringBuffer在append的時(shí)候呢,做同步,因此當(dāng)你去使用這種方法的時(shí)候呢,或者使用這種同步類(lèi)的時(shí)候呢,實(shí)際上你內(nèi)部自然會(huì)把這個(gè)鎖引進(jìn)去,但是有時(shí)候你自己也察覺(jué)不到,你使用Vector你認(rèn)為這是很自然的事情,但是你可能用在完成不可能同步現(xiàn)象,不可能被多線程訪問(wèn)的場(chǎng)景之中,那么在這種情況之下呢,為了提高系統(tǒng)性能,系統(tǒng)會(huì)把鎖給優(yōu)化掉,比如說(shuō)像這種情況,我們?cè)谶@個(gè)地方new了一個(gè)StringBuffer,后來(lái)對(duì)他做了一些修改,這兩個(gè)操作都是同步操作,大家可以確認(rèn)一下,現(xiàn)在是說(shuō)StringBuffer的實(shí)例sb,他是否有可能被多個(gè)線程訪問(wèn)呢,那顯然是不可能的,我們并沒(méi)有把sb返回,而是sb.toString得到的值,是否有其他的線程可能訪問(wèn)到sb呢,不可能的,因?yàn)閟b他是一個(gè)局部變量,局部變量他就是在線程內(nèi)部,引用是在線程的棧空間當(dāng)中的,就是局部變量表里面,其他的線程呢,不可能訪問(wèn)到這個(gè)實(shí)例上去,因此對(duì)于sb變量來(lái)說(shuō),他一定只有這一個(gè)線程可以訪問(wèn),因此對(duì)于他所有的同步操作呢,都是沒(méi)有意義的,對(duì)于這種情況,虛擬機(jī)就有可能優(yōu)化

你必須開(kāi)啟一個(gè)server模式,server模式要比client模式更多的優(yōu)化,同時(shí)要做逃逸分析,必須要開(kāi)啟逃逸分析

就是sb有沒(méi)有可能逃出作用域,如果這個(gè)函數(shù)最后返回了StringBuffer,如果沒(méi)有sb.toString,直接return StringBuffer,sb在return之后呢,他就有可能變成一個(gè)公有性的一個(gè)變量,導(dǎo)致系統(tǒng)任何地方都有可能被訪問(wèn)到,sb逃出了原本的sb范圍,我們就可以做一個(gè)鎖消除,一旦我認(rèn)為你沒(méi)有逃出去,我的線程不會(huì)被多個(gè)線程訪問(wèn),逃出去之后我就不能做這個(gè)保證了,只要沒(méi)有逃出去我還可以保證說(shuō),你不會(huì)被多個(gè)線程訪問(wèn),因此我可以把里面進(jìn)行的加鎖操作,同步給去掉,這個(gè)就表示把這個(gè)鎖給去掉,我要進(jìn)行逃逸分析,這個(gè)是基于逃逸分析才能做的,不開(kāi)啟鎖消除和開(kāi)啟鎖消除性能還是有關(guān)系的,有一個(gè)是相當(dāng)完全沒(méi)有鎖,你還要去不停的做鎖的判斷,你肯定要多執(zhí)行幾條指令,這個(gè)就是關(guān)于鎖消除的一個(gè)概念,下面我們看一下虛擬機(jī)內(nèi)部對(duì)鎖的優(yōu)化,說(shuō)白了當(dāng)你使用synchronized關(guān)鍵字的時(shí)候呢,看里面會(huì)做些什么事情,虛擬機(jī)對(duì)鎖的優(yōu)化是希望,進(jìn)行阻塞性的線程同步的時(shí)候,希望可以獲得更好的性能,我們首先來(lái)看一下鎖偏向

我們先簡(jiǎn)單的介紹一下鎖對(duì)象頭,我們知道在JAVA當(dāng)中有很多的對(duì)象,虛擬機(jī)中對(duì)象有個(gè)對(duì)象頭,對(duì)象頭在32位系統(tǒng)當(dāng)中是32位的,對(duì)象頭是32位的一個(gè)標(biāo)記,可以存放很多信息,比如對(duì)象的hash,鎖的信息,回收標(biāo)記,年齡,偏向鎖ID,大家要知道一個(gè)對(duì)象他頭部呢,是會(huì)保存一些系統(tǒng)性的信息,這個(gè)對(duì)象頭部呢

什么叫偏向鎖呢,他很偏心,它會(huì)偏向于當(dāng)前已經(jīng)占有鎖的線程,它會(huì)判斷一下,你自己當(dāng)前請(qǐng)求這個(gè)鎖的線程,是不是已經(jīng)有了這把鎖了,有的時(shí)候會(huì)出現(xiàn)一個(gè)線程不停的去請(qǐng)求,同一把鎖,這個(gè)是完全有可能的,它是基于一個(gè)什么樣的思想呢,我們要承認(rèn)一點(diǎn),鎖它是一種悲觀的策略,我們會(huì)想到說(shuō),我們可能會(huì)發(fā)生數(shù)據(jù)競(jìng)爭(zhēng),可能會(huì)有沖突,可能會(huì)有線程安全問(wèn)題,所以我要加鎖,事實(shí)上你這個(gè)沖突的概率有多大,這是不好說(shuō)的,如果你這個(gè)負(fù)載不是很重,一般的情況之下吧,很多情況競(jìng)爭(zhēng)是根本不存在的,我們可能是杞人憂天,如果這個(gè)競(jìng)爭(zhēng)是不存在的,或者競(jìng)爭(zhēng)不是非常激烈,那么偏向鎖就可以用來(lái)提高鎖的性能,因?yàn)橐粋€(gè)對(duì)象持有對(duì)象鎖之后呢,他下一次在進(jìn)去的時(shí)候呢,他就會(huì)判斷說(shuō),我是不是處于偏向模式,如果處于偏向模式,我就不需要任何鎖的操作了,直接就進(jìn)入到鎖里面去了,這樣就可以提高我進(jìn)入鎖的速度,系統(tǒng)的性能也就提高了,如果沒(méi)有競(jìng)爭(zhēng),只是你一個(gè)線程反復(fù)的進(jìn)一把鎖,那么偏向鎖就對(duì)性能帶來(lái)很高的提升,我們把對(duì)象頭部的mark,設(shè)置為偏向模式,拿到鎖的對(duì)象ID到對(duì)象頭部,只要沒(méi)有競(jìng)爭(zhēng),獲得偏向鎖的線程,在將來(lái)進(jìn)入同步塊,不需要做同步的,就可以直接進(jìn)去,只要看線程ID是不是我自己,當(dāng)有其他線程請(qǐng)求相同鎖的時(shí)候呢,這個(gè)偏向模式就要宣告結(jié)束,這個(gè)時(shí)候就要參與競(jìng)爭(zhēng),你不能直接進(jìn)去,我們可以使用UseBaisedLocking,來(lái)啟用這個(gè)偏向鎖,默認(rèn)它是啟用的,另外一點(diǎn)要注意的是,在競(jìng)爭(zhēng)激烈的場(chǎng)合,偏向鎖是會(huì)增加系統(tǒng)的負(fù)擔(dān)的,如果你每次偏向模式,都會(huì)失敗,你每次都要把偏向模式給移除掉,意味著我們要多了一次是否要偏向的判斷,因此偏向鎖不太會(huì)有什么效果,所以任何事情他都是有兩面性的

這里是偏向鎖的一個(gè)例子,那我們可以看到,這里我們是在一個(gè)線程當(dāng)中,這個(gè)主線程當(dāng)中,numberList里面是有這個(gè)鎖的,去加一個(gè)數(shù)據(jù),最后我們打印出時(shí)間,我們可以使用偏向鎖,并且把偏向鎖StartupDelay設(shè)為0,因?yàn)橄到y(tǒng)起來(lái)的時(shí)候呢,偏向模式并不會(huì)啟動(dòng),在系統(tǒng)起來(lái)的幾秒鐘之內(nèi),默認(rèn)的時(shí)間我記不清了,系統(tǒng)起來(lái)4秒鐘之內(nèi),其實(shí)所有的線程同步,不會(huì)去使用偏向鎖,都不會(huì)使用偏向鎖,因?yàn)樵谶@個(gè)時(shí)候呢,我剛剛啟動(dòng),我又好多事情要做,因此我的這個(gè)數(shù)據(jù)競(jìng)爭(zhēng),可能是比較激烈的,因此我沒(méi)有必要使用偏向鎖這個(gè)模式,但是當(dāng)我4秒鐘之后,虛擬機(jī)開(kāi)啟之后,我的偏向模式才能真正啟動(dòng),因此這段代碼我要測(cè)出來(lái)呢,我就必須把StartupDelay時(shí)間設(shè)置為0,也就是說(shuō),系統(tǒng)起來(lái)之后,不能等我起來(lái)4秒鐘之后才啟用,我們一看這個(gè)代碼很簡(jiǎn)單,它是在短短幾秒鐘之內(nèi)就能結(jié)束的,所以我希望在進(jìn)入到main函數(shù)的時(shí)候呢,這個(gè)偏向鎖就 已經(jīng)啟用了,而不需要花太多等待的時(shí)間,這是這個(gè)參數(shù)的含義,然后這是把偏向鎖給啟用了,然后這里是禁用,大家可以去試一下,如果你啟用偏向鎖和禁用偏向鎖性能的提升呢,基本上可以穩(wěn)定在5%以上,有這么一個(gè)效果

下面我們來(lái)看一下輕量級(jí)鎖,BasicObjectLock,就是我們這個(gè)Lock,輕量級(jí)鎖他大概是一個(gè)什么樣的東西呢,如果我偏向鎖失敗,就有可能去做輕量級(jí)鎖的操作

進(jìn)來(lái)不要去用操作系統(tǒng)層面的互斥,因?yàn)槟莻€(gè)性能會(huì)比較差,對(duì)于操作系統(tǒng)來(lái)講,虛擬機(jī)本身他就是一個(gè)應(yīng)用,所以我們希望在我們應(yīng)用層面呢,去解決這個(gè)線程同步的問(wèn)題,輕量級(jí)鎖就是這個(gè)思想,我判斷一個(gè)對(duì)象是否持有對(duì)象鎖,我只要看看說(shuō),我這個(gè)鎖lock,前面看到的BasicLock,頭部是不是設(shè)了mark,如果是的話,我就可以認(rèn)為我這個(gè)線程就持有了這把鎖,他就是會(huì)做兩個(gè)事情,將對(duì)象頭的mark保存到鎖對(duì)象當(dāng)中,對(duì)象頭設(shè)置為指向鎖的指針,同時(shí)大家注意的是,鎖的位置是放在線程棧空間當(dāng)中,這個(gè)時(shí)候我判斷對(duì)象是否持有這把鎖的時(shí)候呢,因?yàn)閷?duì)象頭部我們剛剛說(shuō)了,頭部會(huì)指向這個(gè)鎖,而鎖又在線程棧當(dāng)中,我要判斷一個(gè)線程是否持有這把鎖,要看頭部所指向的位置,是不是線程棧的地址當(dāng)中,如果是我就認(rèn)為你持有這把鎖,輕量級(jí)鎖,虛擬機(jī)層面,來(lái)給你一個(gè)快速的判斷,所以這里使用的也是一個(gè)CAS操作,如果CAS失敗了那就證明你鎖失敗了,成功你就持有這把鎖了

如輕量級(jí)鎖獲取失敗,表示你存在著競(jìng)爭(zhēng),你就要升級(jí)為重量級(jí)鎖,就是我們說(shuō)的常規(guī)鎖,那可能就要?jiǎng)佑貌僮飨到y(tǒng)層的方法了,那在沒(méi)有競(jìng)爭(zhēng)的條件之下呢,操作系統(tǒng)的互斥的消耗,同樣道理,在競(jìng)爭(zhēng)非常激烈的場(chǎng)合,什么叫非常激烈呢,如果你輕量級(jí)鎖的申請(qǐng),總是會(huì)失敗的,或者失敗的概率非常高,這個(gè)時(shí)候輕量級(jí)鎖相當(dāng)于多做了很多的事情,這樣也會(huì)導(dǎo)致性能的下降

下面我們來(lái)看一下自旋鎖,如果說(shuō)我輕量級(jí)鎖失敗了,他就有可能會(huì)去動(dòng)用操作系統(tǒng)層面的互斥,我這個(gè)說(shuō)是有可能,那也有可能不去動(dòng)用,為什么有可能不去動(dòng)用呢,那是因?yàn)樵谳p量級(jí)鎖失敗之后,他還會(huì)進(jìn)行自旋鎖,有關(guān)自旋鎖大家也不會(huì)陌生,我們?cè)贑oncurrentHashMap,去解析put方法是怎么執(zhí)行當(dāng)中呢,也有講過(guò)這個(gè)自旋,如果說(shuō)自旋操作有發(fā)現(xiàn)說(shuō),這個(gè)鎖已經(jīng)被別人拿走了,這個(gè)時(shí)候我們會(huì)看到,ConcurrentHashMap并沒(méi)有直接把自己掛起,它會(huì)去做一個(gè)trylock,trylock他沒(méi)有阻塞,他只是一個(gè)簡(jiǎn)單的CAS操作,鎖里面有一個(gè)AtomicInteger,表示加鎖的這個(gè)字段呢,我們是不是能夠再把它拿到,拿不到我們會(huì)去做一個(gè)循環(huán),在這個(gè)循環(huán)當(dāng)中呢,我們會(huì)去不斷地嘗試,重新會(huì)去做trylock操作,那個(gè)東西就是一個(gè)自旋,當(dāng)你拿不到鎖的時(shí)候,不要急著把這個(gè)線程給掛起,因?yàn)槟阒苯影堰@個(gè)線程掛起,要消耗大概8萬(wàn)個(gè)CPU的時(shí)間周期,成本是比較高的,因此在這個(gè)時(shí)候呢,我讓這個(gè)線程做幾個(gè)空循環(huán),和ConcurrentHashMap里面的put方法做的事情是一樣的,我做幾個(gè)空循環(huán),在每一次循環(huán)當(dāng)中,我讀取嘗試獲取,這個(gè)trylock,當(dāng)我超過(guò)了這個(gè)循環(huán)次數(shù),我1千次之后還沒(méi)有拿到trylock,那我就把它給掛起,那么這個(gè)地方也是一樣的,在虛擬機(jī)內(nèi)部,當(dāng)你輕量級(jí)鎖也沒(méi)有拿到鎖的時(shí)候呢,那他就會(huì)去做最后的嘗試,就是自旋,自旋鎖會(huì)做空循環(huán),并且不停地去嘗試獲取這個(gè)鎖,當(dāng)你持有這個(gè)鎖的時(shí)間不是特別長(zhǎng)的時(shí)候,有一條基本的優(yōu)化原則呢,減少鎖的持有時(shí)間,只有你持有鎖的時(shí)間不是特別長(zhǎng),所以你這個(gè)空循環(huán)呢,別人就把這個(gè)鎖釋放掉了,因此你就可以很順利把這個(gè)鎖給拿到,如果說(shuō)你在這個(gè)地方把鎖給拿到了,那你就可以避免在這個(gè)操作層面被掛起,避免線程在操作層面被掛起,性能就會(huì)有所提升,我們?cè)倩叵隒oncurrentHashMap,HashMap做的事情是很簡(jiǎn)單的,把這個(gè)數(shù)據(jù)做一個(gè)計(jì)算,把它復(fù)制到數(shù)組當(dāng)中,這些操作,都是非常快的,而并沒(méi)有像鏈表一樣,鏈表很長(zhǎng)的時(shí)候我要一個(gè)一個(gè)遍歷下去,他都是很快的去操作,因此我們認(rèn)為HashMap的put方法,并不會(huì)占用太多的時(shí)間,而這個(gè)時(shí)間是比較短的,因此對(duì)于這種短促的操作呢,你自旋的成功就會(huì)比較高,回應(yīng)了減少鎖的持有時(shí)間,當(dāng)你減少鎖的持有時(shí)間之后呢,你自旋的成功率就會(huì)上升,就更能避免那8萬(wàn)個(gè)時(shí)間周期的浪費(fèi),那么在JDK6的時(shí)候呢,他還是有一個(gè)UseSpining的操作,來(lái)開(kāi)啟和關(guān)閉自旋鎖,后來(lái)大家覺(jué)得這個(gè)是一個(gè)非常好的東西,我們不應(yīng)該把他關(guān)掉,所以JDK1.7當(dāng)中呢,就沒(méi)有這個(gè)參數(shù)了,就變成了內(nèi)置的必然的一個(gè)實(shí)現(xiàn),這也可以進(jìn)一步證明,自旋鎖對(duì)性能的影響,如果說(shuō)同步塊很長(zhǎng),自旋就會(huì)經(jīng)常失敗,會(huì)降低系統(tǒng)性能,如果同步塊很短,自旋成功率上升,節(jié)省線程掛起的時(shí)間,系統(tǒng)的性能就會(huì)有提高,這就是自旋鎖,這些在虛擬機(jī)層面,JAVA虛擬機(jī)在做鎖的時(shí)候呢,它會(huì)盡可能的做一些優(yōu)化

不是JAVA語(yǔ)言層面的方法,它是虛擬機(jī)層面所做的優(yōu)化,它是內(nèi)置在JVM當(dāng)中的,虛擬機(jī)會(huì)先嘗試偏向鎖,它會(huì)使用輕量級(jí)鎖,如果以上都失敗呢,它會(huì)嘗試自旋鎖,再失敗就嘗試普通鎖,使用操作系統(tǒng)互斥量在操作系統(tǒng)層掛起

下面我們來(lái)看一下使用鎖錯(cuò)誤的案例,這個(gè)也是以前在JAVA虛擬機(jī)中,經(jīng)常會(huì)犯的一個(gè)錯(cuò)誤,我們先看看這段代碼,這段代碼是做i++操作,兩個(gè)線程i++,如果運(yùn)行正常的話,這個(gè)i應(yīng)該是20萬(wàn),但是事實(shí)上如果執(zhí)行這段代碼呢,加起來(lái)可能不到20萬(wàn),就說(shuō)明這個(gè)系統(tǒng)是有問(wèn)題的,但是這段代碼為什么是不對(duì)的呢,我們可以看到,我們?cè)趇++之前拿到了一把鎖,并且執(zhí)行了i++操作,看起來(lái)很正常,因?yàn)槲以谧鰅++之前,我明明已經(jīng)拿到這把鎖了,那我對(duì)他做i++其他線程進(jìn)不來(lái),這個(gè)問(wèn)題有什么問(wèn)題呢,看起來(lái)是沒(méi)有問(wèn)題的,但是實(shí)際上是有錯(cuò)誤的,是因?yàn)橹癐nteger我們之前說(shuō)過(guò),它是一個(gè)不變對(duì)象,他是一個(gè)不變模式,對(duì)于一個(gè)不變模式來(lái)說(shuō),你對(duì)他做++,他能改變嗎,它是不能改變的,因此i++實(shí)際做的是一個(gè)什么動(dòng)作呢,因此它改變的不是Integer這個(gè)對(duì)象,對(duì)象的這個(gè)值,他改變的是i本身的引用,當(dāng)你在做i++之后呢,它會(huì)生成一個(gè)新的int值,然后把Integer賦值到i上面去,而不是把原來(lái)的i做++操作,如果這樣說(shuō)就能夠說(shuō)的通了,如果你每次只對(duì)i做同步,但是你不同的線程,你不同的兩個(gè)線程之間,你所同步的對(duì)象呢,你未必是一樣的,前一個(gè)線程可能是i,而后一個(gè)線程可能新建了一個(gè)i,大家對(duì)不同的i做++操作,因此得到的結(jié)果可能是完全不一樣的,看看這個(gè)代碼是不是線程安全的執(zhí)行,自己去研究一下

之所以在這里說(shuō)一下ThreadLocal呢,ThreadLocal也是一個(gè)比較重要的概念,同時(shí)它跟鎖是沒(méi)有關(guān)系的,我們講了很多鎖優(yōu)化的問(wèn)題,它是可以把鎖完全可以踢掉的一個(gè)東西,他有點(diǎn)像什么呢,我們?cè)诙嗑€程當(dāng)中,我們有數(shù)據(jù)沖突的對(duì)象,進(jìn)行加鎖,把鎖去掉最簡(jiǎn)單的一種方法呢,就是為每一個(gè)線程都提供一個(gè)對(duì)象實(shí)例,不同的線程他訪問(wèn)他自己的對(duì)象,而不去訪問(wèn)別人的對(duì)象,那這個(gè)時(shí)候鎖就完全沒(méi)有必要存在,這個(gè)就是ThreadLocal的基本思想,從這個(gè)名字上來(lái)看呢,線程局部的變量,線程局部,local局部,他就是線程局部的一個(gè)變量,我們可以看看這個(gè)例子,這里我們使用一個(gè)SimleDateFormat,它是用來(lái)格式化時(shí)間的,這里我們是調(diào)用了parse方法,去解析一個(gè)時(shí)間,然后我們對(duì)時(shí)間做一個(gè)打印,把一個(gè)字符串換成時(shí)間,但是如果我們這樣寫(xiě)代碼,我們共用這么一個(gè)變量實(shí)例,其實(shí)這個(gè)代碼不可以這樣執(zhí)行,原因呢是因?yàn)閟df,這個(gè)SimpleDateFormat他不是線程安全的,所以當(dāng)你去對(duì)他做操作的時(shí)候,他被多個(gè)線程訪問(wèn)的時(shí)候,他就可能會(huì)拋出一些異常,當(dāng)我數(shù)據(jù)沖突的時(shí)候不能正常的工作,代碼本身是錯(cuò)誤的,最簡(jiǎn)單的是改成線程安全的就可以了,怎么改呢,我們只要包裝一個(gè)synchronized,這樣的說(shuō)法是不錯(cuò)的,確實(shí)可以這樣做,但是這樣做在高并發(fā)的時(shí)候會(huì)有一些問(wèn)題,每一次你只能進(jìn)去一個(gè)線程,并不是每一次可以進(jìn)去好多線程,因此一個(gè)比較靠譜的方法呢,就是使用ThreadLocal,封裝sdf

我們先聲明一個(gè)tl,里面就裝著SimpleDateFormat,然后我們?cè)诿恳粋€(gè)線程當(dāng)中,做件什么事情呢,如果你這個(gè)線程沒(méi)有這個(gè)變量,get方法就是拿到當(dāng)前線程的SimpleDateFormat,如果沒(méi)有的話我就把這個(gè)對(duì)象給設(shè)進(jìn)去,然后解析我就拿當(dāng)前線程的對(duì)象,來(lái)做解析,然后我把數(shù)據(jù)拿出來(lái)就可以了,如果你這樣做會(huì)有一個(gè)后果就是,我每一個(gè)線程,比如我這里有10個(gè)線程,我每一個(gè)線程都去創(chuàng)建一個(gè)SimpleDateFormat對(duì)象,每一個(gè)線程都會(huì),同時(shí)我調(diào)用parse的時(shí)候呢,我一定不會(huì)有多線程的問(wèn)題,每個(gè)線程只會(huì)訪問(wèn)自己的對(duì)象,這種寫(xiě)法就是線程安全的,另外一個(gè)錯(cuò)誤使用案例呢,我還是在外面使用一個(gè)大的sdf,然后我把它設(shè)置到ThreadLocal里面去,然后每一次也是從ThreadLocal里面拿,然后去做這個(gè)解析,這樣的寫(xiě)法都是不對(duì)的,正確的寫(xiě)法是這樣,每一個(gè)線程都要new一個(gè)出來(lái),ThreadLocal只維護(hù)我線程里的對(duì)象,但是他不會(huì)去維護(hù),每一個(gè)對(duì)象的拷貝,他只把源對(duì)象給設(shè)置進(jìn)去了,我把這個(gè)對(duì)象復(fù)制一份,他直接把這個(gè)對(duì)象的數(shù)據(jù)直接指向sdf,因此這種寫(xiě)法看起來(lái)使用了ThreadLocal,但實(shí)際上你只是說(shuō),對(duì)每一個(gè)ThreadLocal的對(duì)象呢,你都把他指向同一個(gè)對(duì)象實(shí)例,因此get出來(lái)的東西呢,他也是一個(gè)同一個(gè)對(duì)象實(shí)例,他還不是對(duì)象安全的,就會(huì)出錯(cuò)的,這個(gè)是使用ThreadLocal要特別注意的地方,如果你希望,每個(gè)線程都有它自己的對(duì)象呢,你每次都要去new一個(gè),而不要把一個(gè)公共的東西給設(shè)置進(jìn)去,Hibernate在保存connection的時(shí)候,也是使用了ThreadLocal這個(gè)東西,公共的類(lèi),工具類(lèi),數(shù)據(jù)庫(kù)連接,每個(gè)線程單獨(dú)持有一個(gè),就可以減少數(shù)據(jù)同步的開(kāi)銷(xiāo),SimpleDateFormat這種工具類(lèi),他不需要像Vector一樣,多個(gè)線程之間還有相互影響,也就是一些工具性的API,他也不會(huì)有這種沖突就可以做這樣的事情

那我們來(lái)看一下ThreadLocal的實(shí)現(xiàn),它內(nèi)部提供了一個(gè)怎樣的實(shí)現(xiàn),這里就是ThreadLocal的一個(gè)變量,一個(gè)重要的方法就是set/*** Sets the current thread's copy of this thread-local variable* to the specified value. Most subclasses will have no need to* override this method, relying solely on the {@link #initialValue}* method to set the values of thread-locals.** @param value the value to be stored in the current thread's copy of* this thread-local.*/ public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value); }我們把一個(gè)值set到ThreadLocal里面去,我們可以看到這里,tl set的時(shí)候,我們把這個(gè)對(duì)象的實(shí)例set進(jìn)去,他里面是怎么做的呢,/*** Sets the current thread's copy of this thread-local variable* to the specified value. Most subclasses will have no need to* override this method, relying solely on the {@link #initialValue}* method to set the values of thread-locals.** @param value the value to be stored in the current thread's copy of* this thread-local.*/ public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value); }首先拿到這個(gè)線程,拿到這個(gè)線程的ThreadLocalMap,什么叫ThreadLocalMap,這是定義在線程當(dāng)中的字段,所謂的線程局部變量,它本身就隸屬于線程,Thread里面就有一個(gè)ThreadLocalMap,/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;這是在線程當(dāng)中的,提供了內(nèi)部所有的奧秘,當(dāng)拿到這個(gè)map之后,每個(gè)線程都有自己的map,如果map不等于null,他就把這個(gè)值設(shè)上去,否則把map新建起來(lái),當(dāng)把這個(gè)值設(shè)上去的時(shí)候呢,我們可以看到,這個(gè)map有個(gè)key有個(gè)value,這個(gè)和普通的map是非常的類(lèi)似的,這個(gè)key是什么呢,就是我們ThreadLocal本身,/*** Set the value associated with key.** @param key the thread local object* @param value the value to be set*/ private void set(ThreadLocal<?> key, Object value) {// We don't use a fast path as with get() because it is at// least as common to use set() to create new entries as// it is to replace existing ones, in which case, a fast// path would fail more often than not.Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();if (k == key) {e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash(); }相當(dāng)于說(shuō)我們每個(gè)線程,某一個(gè)ThreadLocal變量,他的這個(gè)值是多少,因此當(dāng)你去get的時(shí)候呢,我們也可以執(zhí)行g(shù)et,/*** Returns the value in the current thread's copy of this* thread-local variable. If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local*/ public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue(); }getEntry我們只要把this傳進(jìn)去就可以了,因?yàn)槲覀兊膋ey就是ThreadLocal本身,get是調(diào)用ThreadLocal上面的,我們把key this傳進(jìn)去,entry拿出來(lái),然后就可以把value做一個(gè)返回了,之前我們有對(duì)HashMap的實(shí)現(xiàn),這里也是基本類(lèi)似的,整個(gè)Map本身的實(shí)現(xiàn)呢,也會(huì)依賴(lài)于一個(gè)數(shù)組,Entry數(shù)組,Entry里面有什么呢,/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object). Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table. Such entries are referred to* as "stale entries" in the code that follows.*/ static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;} }另外一個(gè)比較重要的是什么呢,Entry它是繼承弱引用的,如果說(shuō)你沒(méi)有數(shù)據(jù),引用到這個(gè)對(duì)象的話呢,這個(gè)對(duì)象本身就會(huì)被釋放掉,我們看set這個(gè)方法

?

總結(jié)

以上是生活随笔為你收集整理的锁的优化和注意事项的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。