Java多線程中Lock鎖的使用總結(jié)
多核時(shí)代
摩爾定律告訴我們:當(dāng)價(jià)格不變時(shí),集成電路上可容納的晶體管數(shù)目,約每隔18個(gè)月便會(huì)增加一倍,性能也將提升一倍。換言之,每一美元所能買(mǎi)到的電腦性能,將每隔18個(gè)月翻兩倍以上。然而最近摩爾定律似乎遇到了麻煩,目前微處理器的集成度似乎到了極限,在目前的制造工藝和體系架構(gòu)下很難再提高單個(gè)處理器的速度了,否則它就被燒壞了。所以現(xiàn)在的芯片制造商改變了策略,轉(zhuǎn)而在一個(gè)電路板上集成更多的處理器,也就是我們現(xiàn)在常見(jiàn)的多核處理器。
這就給軟件行業(yè)帶來(lái)麻煩(也可以說(shuō)帶來(lái)機(jī)會(huì),比如說(shuō)就業(yè)機(jī)會(huì),呵呵)。原來(lái)的情況是:我買(mǎi)一臺(tái)頻率比原來(lái)快一倍的處理器,那么我的程序就比原來(lái)快一倍,軟件工程師什么也不用干?,F(xiàn)在不一樣了,我買(mǎi)一臺(tái)雙核的處理器,我的程序和原來(lái)一樣慢,當(dāng)然這條機(jī)器同時(shí)處理的任務(wù)可以變多了,但是對(duì)于單個(gè)任務(wù)來(lái)說(shuō)并沒(méi)有幫助。
在幾年前,并發(fā)(Concurrent)和并行(Paralleling)程序設(shè)計(jì)還是在少量的地方使用,現(xiàn)在在個(gè)人的PC機(jī)上已經(jīng)是很常見(jiàn)了。(Concurrency and parallelism的區(qū)別參考 這個(gè)帖子)
造個(gè)諸葛亮的價(jià)錢(qián)遠(yuǎn)遠(yuǎn)高于造三個(gè)臭皮匠!多核是在一臺(tái)機(jī)器上的并發(fā),但是單機(jī)也是會(huì)到極限,所以分布式的計(jì)算也是類(lèi)似的思路,用大量普通的機(jī)器協(xié)作完成一項(xiàng)任務(wù)。
但是要想編寫(xiě)一個(gè)正確并且高效的能利用多核的多線程程序不是件容易的是,更別說(shuō)分布式的情況(網(wǎng)絡(luò)問(wèn)題,機(jī)器故障,負(fù)載均衡,。。。)?,F(xiàn)在的編譯器沒(méi)有辦法把單線程的程序自動(dòng)編譯成一個(gè)多線程的版本(如果到了那一天,估計(jì)所有的程序員就失業(yè)了)。所以只能提供一些語(yǔ)言上的支持(比如scala/erlang)或者mapreduce這樣的框架。
Java雖然沒(méi)有提供scala那樣的基于消息的模型,但是也提供了豐富的concurrent特性,并且屏蔽了平臺(tái)的相關(guān)性(這不是件容易的事,比如多個(gè)處理器有自己的緩存,他們寫(xiě)的東西不會(huì)離開(kāi)被其它處理器看到),下面我們看看java的內(nèi)存模型(JMM)
JMM(Java Memory Model)
并行程序有很多模型,比如共享內(nèi)存模型,消息傳遞模型等等。這些模型或多或少的利用了平臺(tái)相關(guān)的特性(在并行程序設(shè)計(jì)里很難回避平臺(tái)的特性以便高效的通信),Java抽象出了自己的內(nèi)存模型,使得開(kāi)放人員看不到平臺(tái)的差異(這不是件容易的事),不過(guò)即使這樣,和傳統(tǒng)程序不同,我們還是不能完全不了解一些體系架構(gòu)的細(xì)節(jié)問(wèn)題,至少我們得了解一些。
在共享內(nèi)存的多處理器體系架構(gòu)里(我們現(xiàn)在用的服務(wù)器甚至筆記本都是),每個(gè)處理器都有自己的局部緩存并定期的使之與內(nèi)存同步。不同的處理器架構(gòu)保證了不同程度的緩存一致性(cache coherence),所以操作系統(tǒng),編譯器和運(yùn)行時(shí)環(huán)境必須一起努力來(lái)彌補(bǔ)平臺(tái)的差異性。
讓每個(gè)處理器都知道其它處理器的狀態(tài)的代價(jià)是非常昂貴的,所以大多數(shù)架構(gòu)都不會(huì)保證一致性,這通常不會(huì)有什么問(wèn)題:進(jìn)程/線程直接并不共享信息,編譯器可以調(diào)整代碼執(zhí)行順序以便提高效率,我們都很開(kāi)心。當(dāng)然也有需要在線程之間進(jìn)行同步的時(shí)候,比如某個(gè)線程要讀取到另一個(gè)線程寫(xiě)入的信息,這個(gè)時(shí)候緩存里的數(shù)據(jù)就得同步到內(nèi)存里才行。所以這些體系架構(gòu)都提供了一些指令來(lái)完成數(shù)據(jù)的同步(當(dāng)然這些指令是非常費(fèi)時(shí)的,能不做就盡量不做)。這些指令一般叫做memory barriers or fences。當(dāng)然只是很底層的一些東西,所幸Java提供了一些高層的抽象,讓我們的生活變得容易一些。
sequential consistency: 我們假設(shè)一個(gè)線程執(zhí)行(可能在多個(gè)處理器上切換),每個(gè)變量讀取到的值都是最新的修改(也就是Cache里的立馬生效),這樣得到的結(jié)果是我們預(yù)期的。
但是讓我們意外的事情是:如果我們不做任何事情,那么很可能會(huì)出現(xiàn)錯(cuò)誤,比如下面的這個(gè)例子:
public class NoVisibility { private static boolean ready; private static int number; private static class ReaderThread extends Thread { public void run() { while (!ready) Thread.yield(); System.out.println(number); } } public static void main(String[] args) { new ReaderThread().start(); number = 42; ready = true; } }
我們?cè)谥骶€程里先讓number=42(初始值是0),然后讓ready=true,而另一個(gè)線程不斷堅(jiān)持是否ready,如果ready,那么讀出number。很自然的我們期望子線程打印出42,但是很可能結(jié)果會(huì)另我們失望。編譯器可能會(huì)調(diào)換number=42 和 ready=true的順序(思考一下為什么它要這么干?為什么在單線程的情況下沒(méi)有問(wèn)題?),另外子線程可能永遠(yuǎn)在while里死循環(huán)。為什么?子線程會(huì)永遠(yuǎn)看不到ready的變化?這也許讓很多人吃驚,事實(shí)確實(shí)如此,JSR并不保證這一點(diǎn)(雖然大多數(shù)時(shí)候子線程能夠退出),參考這個(gè)帖子和JMM的文章
vilatile和snychronized(intrinsic Lock)
vilatile關(guān)鍵字告訴編譯器,一個(gè)線程對(duì)某個(gè)變量的修改立即對(duì)所有其它線程看見(jiàn),加上這個(gè)能保證上面的程序不會(huì)死循環(huán)。但是不能保證讀到42,也就是保證number=42和ready=true的執(zhí)行順序,要保證這點(diǎn)就要用到synchronized。
synchronized能夠保證執(zhí)行的順序,除此之外,它也能保證可見(jiàn)性。
public class NoVisibility { private static boolean ready; private static int number; private static class ReaderThread extends Thread { public void run() { boolean r=false; while (true){ synchronized(NoVisibility.class){ r=ready; } if(r) break; else Thread.yield(); } System.out.println(number); } } public static void main(String[] args) { new ReaderThread().start(); synchronized(NoVisibility.class){ number = 42; ready = true; } } }
synchronized(NoVisibility.class){ number = 42; ready = true; }
這段代碼保證了兩個(gè)語(yǔ)句的執(zhí)行順序
synchronized(NoVisibility.class){ r=ready; }
這保證子線程能看到ready的變化 注意他們必須synchronized同一個(gè)對(duì)象,如果是下面的代碼,則不能有任何保障。為什么?試想任何synchronized里的變量必須立即對(duì)所有的可見(jiàn),那么代價(jià)太大, 比如我有這樣的需求:我只要求兩個(gè)語(yǔ)句順序執(zhí)行,它是否對(duì)別人可見(jiàn)我并不關(guān)心。
synchronized(AnotherObject){ r=ready; }
每個(gè)對(duì)象都有個(gè)Monitor,所以synchronized也經(jīng)常叫Monitor Lock,另外這個(gè)鎖是語(yǔ)言內(nèi)置的,所以也叫Intrinsic Lock。 這兩個(gè)關(guān)鍵字是java1.5之前就有了,在java1.5之后新引進(jìn)了java.util.concurrent包,這里有我們需要關(guān)注的很多東西,這里我們只關(guān)心Lock相關(guān)的接口和類(lèi)。 不過(guò)synchronized來(lái)解決互斥不是很完美嗎?我為什么要花力氣搞這些新鮮東西呢?下面我們來(lái)看看synchronized解決不了(或者很難解決)的問(wèn)題
銀行轉(zhuǎn)賬的例子
// Warning: deadlock-prone! public void transferMoney(Account fromAccount, Account toAccount, DollarAmount amount) throws InsufficientFundsException { synchronized (fromAccount) { synchronized (toAccount) { if (fromAccount.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); else { fromAccount.debit(amount); toAccount.credit(amount); } } } }
比如我要在兩個(gè)用戶之間轉(zhuǎn)賬,為了防止意外,我必須同時(shí)鎖定兩個(gè)賬戶。但是這可能造成死鎖。比如:
A: transferMoney(myAccount, yourAccount, 10); B: transferMoney(yourAccount, myAccount, 20);
當(dāng)線程A鎖住myAccount時(shí),B鎖住了toAccount,這個(gè)時(shí)候A嘗試鎖住toAccount,但是已經(jīng)被B鎖住,所以A不能繼續(xù)運(yùn)行,同理B也不能運(yùn)行,造成死鎖。
怎么解決呢?你也許回想,我先鎖住一個(gè)賬戶,然后"嘗試"鎖定另一個(gè)賬戶,如果“失敗”,那么我釋放所有的鎖,“休息”一下再繼續(xù)嘗試,當(dāng)然兩個(gè)線程節(jié)拍一致的話,可能造成“活鎖”
可惜synchronized不能提供這樣的語(yǔ)義,它一旦嘗試加鎖,只能拿到鎖,你不能控制它,比如你可能有這樣的需求:嘗試拿鎖30s,如果拿不到就算了,synchronized是沒(méi)辦法滿足這樣的需求的。另外你使用“鴕鳥(niǎo)”策略來(lái)解決死鎖:什么也不干,如果死鎖了,kill他們,重啟他們。這種策略看起來(lái)很瘋狂,不過(guò)如果死鎖的概率很多,而避免死鎖的算法很復(fù)雜,那這也是可以一試的策略(那一堆死鎖發(fā)生的充分必要條件太麻煩了?。。。?。下面我們仔細(xì)的來(lái)看看java1.5后提供的Lock接口及其相關(guān)類(lèi)。
Lock接口
Lock的基本用法如下,為了防止異常退出時(shí)沒(méi)有釋放鎖,一般都在拿到鎖后立馬try,try住所有臨界區(qū)的代碼,然后finally釋放鎖。
主要和synchronized的區(qū)別,synchronized里我們不用操心這些,如果synchronized保護(hù)的代碼拋出異常,那么jvm會(huì)釋放掉Monitor Lock。
Lock l = ... l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
Lock.lock()在鎖定成功后釋放鎖之前,它所保護(hù)的代碼段必須與使用synchronized保護(hù)的代碼段有相同的語(yǔ)義(可見(jiàn)性,順序性)。
所以從這個(gè)角度來(lái)說(shuō),Lock完全可以代替synchronized,那么是否應(yīng)該拋棄掉synchronized呢?答案是否定的。
是否應(yīng)該拋棄synchronized?
在java5引進(jìn)Lock后,實(shí)現(xiàn)了Lock接口的類(lèi)就是ReentrantLock(呆會(huì)再解釋Reentrant),因?yàn)閖ava5之前synchronized的實(shí)現(xiàn)很爛,同樣是為了實(shí)現(xiàn)互斥,ReentrantLock會(huì)比synchronized速度上快很多,不過(guò)到了jdk6之后就不是這樣了,下面是一個(gè)測(cè)試結(jié)果: from book "Java Concurrency in Practice"
橫軸是線程數(shù),縱軸是ReentrantLock的吞吐量/IntrinsicLock的吞吐量。
可以看出,jdk5中,ReentrantLock快很多,但是到了jdk6,他們就沒(méi)什么大的差別了。
synchronized的優(yōu)點(diǎn):鎖的釋放是語(yǔ)言內(nèi)置的,不會(huì)出現(xiàn)忘記釋放鎖的情況,另外由于是語(yǔ)言內(nèi)置的支持,調(diào)試是能很快知道鎖被哪個(gè)線程持有,它加鎖的次數(shù)。而Lock只是util.concurrent一個(gè)普通的類(lèi),所以調(diào)試器并不知道這個(gè)鎖的任何信息,它只是一個(gè)普通的對(duì)象(當(dāng)然你可以仔細(xì)觀察每個(gè)線程的stack frame來(lái)看它在等待鎖)。
所以建議:如果只是為了實(shí)現(xiàn)互斥,那么使用synchronized(扔掉jdk5吧,現(xiàn)在都java7了),如果想用Lock附加的功能,那么才使用Lock。
下面回來(lái)繼續(xù)看Lock接口。
Interface Lock
public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }
void lock();
嘗試獲取鎖。如果鎖被別人拿著,那么當(dāng)前線程不在執(zhí)行,也不能被調(diào)度,直到拿到鎖為止。
void lockInterruptibly() throws InterruptedException
嘗試獲取鎖,除非被interrupted。如果鎖可以獲取,那么立刻返回。
如果無(wú)非獲取鎖,那么線程停止執(zhí)行,并且不能被再調(diào)度,直到:
- 當(dāng)前線程獲得鎖
- 如果鎖的實(shí)現(xiàn)支持interruption,并且有其它線程interrupt當(dāng)前線程。
仔細(xì)閱讀javadoc的第二個(gè)情況:Lock接口并不要求Lock的實(shí)現(xiàn)支持interruption,不過(guò)sun jdk的實(shí)現(xiàn)都是支持的。
這個(gè)函數(shù)在下面兩個(gè)情況下拋出InterruptedException:
- 如果鎖的實(shí)現(xiàn)支持interruption,并且有其它線程interrupt當(dāng)前線程。
- 線程調(diào)用這個(gè)函數(shù)之前就被設(shè)置了interrupted狀態(tài)位
可以發(fā)現(xiàn)這個(gè)方法并不區(qū)分這個(gè)interrupted狀態(tài)位是之前就有的還是lock過(guò)程中產(chǎn)生的。不管如果,拋出異常后會(huì)清除interrupted標(biāo)記。
使用這個(gè)方法,我們可以中斷某個(gè)等鎖的線程,比如我們檢測(cè)到了死鎖,那么我們可以中斷這個(gè)線程
boolean tryLock()
嘗試獲取鎖,如果可以,那么鎖住對(duì)象然后返回true,否則返回false,不管怎么樣,這個(gè)方法會(huì)立即返回。下面的例子展示了用這個(gè)方法來(lái)解決前面轉(zhuǎn)賬的死鎖:
public boolean transferMoney(Account fromAcct, Account toAcct, DollarAmount amount, long timeout, TimeUnit unit) throws InsufficientFundsException, InterruptedException { long fixedDelay = getFixedDelayComponentNanos(timeout, unit); long randMod = getRandomDelayModulusNanos(timeout, unit); long stopTime = System.nanoTime() + unit.toNanos(timeout); while (true) { if (fromAcct.lock.tryLock()) { try { if (toAcct.lock.tryLock()) { try { if (fromAcct.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); else { fromAcct.debit(amount); toAcct.credit(amount); return true; } } finally { toAcct.lock.unlock(); } } } finally { fromAcct.lock.unlock(); } } if (System.nanoTime() < stopTime) return false; NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod); } }
tryLock boolean tryLock(long time, TimeUnit unit) throws InterruptedException
和tryLock類(lèi)似,不過(guò)不是立即返回,而是嘗試一定時(shí)間后還拿不到鎖就返回
unlock
釋放鎖
newCondition
暫且不管
Class ReentrantLock
這是sun jdk(open jdk)里唯一直接實(shí)現(xiàn)了Lock接口的類(lèi),所以如果你想用Lock的那些特性,比如tryLock,那么就應(yīng)該首先考慮它
首先我們解釋一下Reentrant
Reentrant翻譯成中文應(yīng)該是“可重入”,對(duì)于鎖來(lái)說(shuō),可重入是指如果一個(gè)線程已拿到過(guò)一把鎖,那么它可以再次拿到鎖。
聽(tīng)起來(lái)似乎沒(méi)有什么意思,讓我們來(lái)看看“不可重入”鎖可能的一些問(wèn)題和需要使用”可重入“鎖的場(chǎng)景吧。
public class Widget { public synchronized void doSomething() { ... } } public class LoggingWidget extends Widget { public synchronized void doSomething() { System.out.println(toString() + ": calling doSomething"); super.doSomething(); } } Widget widget=new LoggingWidget(); widget.doSomething();
設(shè)想這樣一個(gè)應(yīng)用場(chǎng)景:我們有一個(gè)圖的數(shù)據(jù)結(jié)構(gòu),我們需要遍歷所有節(jié)點(diǎn),找到滿足某些條件的節(jié)點(diǎn),鎖定所有這些節(jié)點(diǎn),然后對(duì)他們進(jìn)行一些操作。由于圖的遍歷可能重復(fù)訪問(wèn)某個(gè)節(jié)點(diǎn),如果簡(jiǎn)單的鎖定每個(gè)滿足條件的節(jié)點(diǎn),那么可能死鎖。當(dāng)然我們可以自己用程序記下哪些節(jié)點(diǎn)已經(jīng)訪問(wèn)過(guò)了,不過(guò)也可以把這就事情交給ReentrantLock,第二次鎖定某個(gè)對(duì)象也會(huì)成功并立即返回。那么你可能會(huì)問(wèn),我釋放鎖的時(shí)候怎么記得它鎖定過(guò)了多少次呢?如果釋放少了,那么會(huì)死鎖;釋放多了,可能也會(huì)有問(wèn)題(有些鎖實(shí)現(xiàn)會(huì)拋出異常,但是JMM好像沒(méi)有定義)。
【上面的場(chǎng)景參考http://stackoverflow.com/questions/1312259/what-is-the-re-entrant-lock-and-concept-in-general】 不用擔(dān)心,ReentrantLock提供了getHoldCount方法,最后釋放這么多次就好了。
ReentrantLock會(huì)記下當(dāng)前拿鎖的線程,已經(jīng)拿鎖的次數(shù),每次unlock都會(huì)減一,如果為零了,那么釋放鎖,另一個(gè)線程到鎖并且計(jì)數(shù)器值為一。
ReentrantLock的構(gòu)造函數(shù)可以接受一個(gè)fairness的參數(shù)。如果為true,那么它會(huì)傾向于把鎖給等待時(shí)間最長(zhǎng)的線程。但是這樣的代價(jià)也是巨大的:
橫軸是并發(fā)線程數(shù),參考方法是ConcurrentHashMap,另外分別用Nonfair Lock和 fair Lock封裝普通的HashMap,可以看到,是否fair的差別是非常巨大的。
正如前面所說(shuō)的,ReentrantLock是支持Interrupted的。
Interface ReadWriteLock
有的應(yīng)用場(chǎng)景下,有兩類(lèi)角色:Reader和Writer。Reader讀取數(shù)據(jù),Writer更新數(shù)據(jù)。多個(gè)Reader同時(shí)讀取是沒(méi)有問(wèn)題的,但是Reader們和Writer是互斥的,并且Writer和Writer也是互斥的。而且很多應(yīng)用中,Reader會(huì)很多,而Writer會(huì)比較少。這個(gè)接口就是為了解決這類(lèi)特殊場(chǎng)景的。
public interface ReadWriteLock { Lock readLock(); Lock writeLock(); } 用法: ReadWriteLock rwl = ...; //Reader threads read(){ rwl.readLock().lock(); try{ //entering critical setion }finally{ rwl.readLock().unlock(); } } write(){ rwl.writeLock().lock(); try{ //entering critical setion }finally{ rwl.writeLock().unlock(); }}
Class ReentrantReadWriteLock
這是Sun jdk里唯一實(shí)現(xiàn)ReadWriteLock接口的類(lèi)。 這個(gè)類(lèi)的特性:
獲取鎖的順序
這個(gè)類(lèi)并不傾向Reader或者Writer,不過(guò)有個(gè)fairness的策略 非公平模式(默認(rèn))
如果很多Reader和Writer的話,很可能Reder一直能獲取鎖,而Writer可能會(huì)饑餓
公平模式
這種模式下,會(huì)盡量以請(qǐng)求鎖的順序來(lái)保證公平性。當(dāng)前鎖釋放以后,等待時(shí)間最長(zhǎng)的Writer或者一組Reader(Reader是一伙的?。┇@取鎖。 如果鎖被拿著,這時(shí)Writer來(lái)了,他會(huì)開(kāi)始排隊(duì);如果Reader來(lái)了,如果它之前沒(méi)有Writer并且當(dāng)前拿鎖的是Reader,那么它直接就拿到鎖,當(dāng)然如果是Writer拿著,那么它也只能排 隊(duì)等鎖。 不過(guò)如果Reader拿著鎖,Writer排隊(duì),然后Reader排在Writer后,但是Writer放棄了排隊(duì)(比如它用的是tryLock 30s),那么Reader直接拿到鎖而不用排隊(duì)。
還有就是ReentrantReadWriteLock.ReadLock.tryLock() 和 ReentrantReadWriteLock.WriteLock.tryLock()方法不管這些,一旦調(diào)用的時(shí)候能拿到鎖,那么它們就會(huì)插隊(duì)??!
Reentrancy
從名字就知道它支持可重入。
以前拿過(guò)鎖的Reader和Writer可以繼續(xù)拿鎖。另外拿到WriteLock的線程可以拿到ReadLock,但是反之不然。
Lock downgrading
拿到WriteLock的可以直接變成ReadLock,不用釋放WriteLock再?gòu)男抡?qǐng)求ReadLock(這樣需要重新排隊(duì)),實(shí)現(xiàn)的方法是先拿到WriteLock,接著拿ReadLock(上面的特性保證了不會(huì)死鎖),然后釋放WriteLock,這樣就得到一個(gè)ReadLock并立馬持有。
Interruption of lock acquisition
支持
一個(gè)使用讀寫(xiě)鎖的例子
class CachedData { Object data; volatile boolean cacheValid; ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); void processCachedData() { rwl.readLock().lock(); if (!cacheValid) { // Must release read lock before acquiring write lock rwl.readLock().unlock(); rwl.writeLock().lock(); // Recheck state because another thread might have acquired // write lock and changed state before we did. if (!cacheValid) { data = ... cacheValid = true; } // Downgrade by acquiring read lock before releasing write lock rwl.readLock().lock(); rwl.writeLock().unlock(); // Unlock write, still hold read } use(data); rwl.readLock().unlock(); } }
一個(gè)Cache數(shù)據(jù)的例子,讀取數(shù)據(jù)時(shí)首先拿讀鎖,如果cache是有效的(volatile boolean cacheValid),直接使用數(shù)據(jù)。
如果失效了,那么釋放讀鎖,獲取寫(xiě)鎖【這個(gè)類(lèi)不支持upgrading】,然后double check一下是否cache有效,如果還是無(wú)效(說(shuō)明它應(yīng)該更新),那么更新數(shù)據(jù),并且修改變量cacheValid,讓其它線程看到。
臭名昭著的double check
前面提到了double check,這里也順便討論一下:
@NotThreadSafe public class DoubleCheckedLocking { private static Resource resource; public static Resource getInstance() { if (resource == null) { synchronized (DoubleCheckedLocking.class) { if (resource == null) resource = new Resource(); } } return resource; } }
很多“hacker”再提到延遲加載的時(shí)候都會(huì)提到它,上面的代碼看起來(lái)沒(méi)有什么問(wèn)題:首先檢查一些resource,如果為空,那么加鎖,因?yàn)闄z查resource==null沒(méi)有加鎖,所以可能同時(shí)兩個(gè)線程進(jìn)入if并且請(qǐng)求加鎖,所以第一個(gè)拿到鎖的初始化一次,第二次拿鎖的會(huì)再次check。這看起來(lái)很完美:大多數(shù)情況下resouce不為空,很少的情況(剛開(kāi)始時(shí))resource為空,那么再加鎖,這比一上來(lái)就加鎖要高效很多不過(guò)千萬(wàn)別高興地太早了,因?yàn)榫幾g器對(duì)引用的賦值可能會(huì)做優(yōu)化,可能這個(gè)對(duì)象還沒(méi)有正確的構(gòu)造好,值已經(jīng)賦好了(為什么要這么做?也許構(gòu)造對(duì)象需要IO,io等待的時(shí)間把值賦好了能提高速度)。這個(gè)時(shí)候別的線程就慘了!另外很多講延遲加載的文章都比較早(早于jdk6),那個(gè)年代java的synchronized確實(shí)很不給力。如果你實(shí)在在乎這點(diǎn)性能的話,應(yīng)該用jvm的靜態(tài)類(lèi)加載機(jī)制來(lái)實(shí)現(xiàn):
@ThreadSafe public class ResourceFactory { private static class ResourceHolder { public static Resource resource = new Resource(); } public static Resource getResource() { return ResourceHolder.resource ; } }
到此這篇關(guān)于Java多線程中Lock鎖的使用總結(jié)的文章就介紹到這了,更多相關(guān)Java多線程 Lock鎖的使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring IOC源碼之bean的注冊(cè)過(guò)程講解
這篇文章主要介紹了Spring IOC源碼之bean的注冊(cè)過(guò)程講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09json序列化時(shí)忽略值為null的字段2種方式實(shí)例
這篇文章主要給大家介紹了關(guān)于json序列化時(shí)忽略值為null的字段的2種方式,當(dāng)對(duì)象中某個(gè)字段為null時(shí),我們希望將對(duì)象轉(zhuǎn)換成json時(shí)為null的字段不會(huì)被轉(zhuǎn)換到j(luò)son字符串,里面需要的朋友可以參考下2023-10-10Java postgresql數(shù)組字段類(lèi)型處理方法詳解
這篇文章主要介紹了Java postgresql數(shù)組字段類(lèi)型處理方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10解決nacos修改配置信息后需要重啟服務(wù)才能生效的問(wèn)題
當(dāng)配置信息發(fā)生變動(dòng)時(shí),傳統(tǒng)修改配置信息后,需要重新重啟服務(wù)器才可以生效,大量應(yīng)用配置修改時(shí),需要一個(gè)個(gè)修改配置,無(wú)法統(tǒng)一修改,且沒(méi)有辦法回溯配置版本,所以本文給大家介紹了如何解決這些問(wèn)題的方法,需要的朋友可以參考下2023-10-10Java OpenSSL生成的RSA公私鑰進(jìn)行數(shù)據(jù)加解密詳細(xì)介紹
這篇文章主要介紹了Java OpenSSL生成的RSA公私鑰進(jìn)行數(shù)據(jù)加解密詳細(xì)介紹的相關(guān)資料,這里提供實(shí)例代碼及說(shuō)明具體如何實(shí)現(xiàn),需要的朋友可以參考下2016-12-12Springboot實(shí)現(xiàn)全局自定義異常的方法詳解
這篇文章主要介紹了Springboot實(shí)現(xiàn)全局自定義異常的方法詳解,SpringBoot的項(xiàng)目已經(jīng)對(duì)有一定的異常處理了,但是對(duì)于我們開(kāi)發(fā)者而言可能就不太合適了,因此我們需要對(duì)這些異常進(jìn)行統(tǒng)一的捕獲并處理,需要的朋友可以參考下2023-11-11