Java的LockSupport.park()实现分析
LockSupport類(lèi)是Java6(JSR166-JUC)引入的一個(gè)類(lèi),提供了基本的線程同步原語(yǔ)。LockSupport實(shí)際上是調(diào)用了Unsafe類(lèi)里的函數(shù),歸結(jié)到Unsafe里,只有兩個(gè)函數(shù):
?park:阻塞當(dāng)前線程(Block?current?thread),字面理解park,就算占住,停車(chē)的時(shí)候不就把這個(gè)車(chē)位給占住了么?起這個(gè)名字還是很形象的。
unpark:?使給定的線程停止阻塞(Unblock?the?given?thread?blocked )。
?
isAbsolute參數(shù)是指明時(shí)間是絕對(duì)的,還是相對(duì)的。
僅僅兩個(gè)簡(jiǎn)單的接口,就為上層提供了強(qiáng)大的同步原語(yǔ)。
先來(lái)解析下兩個(gè)函數(shù)是做什么的。
unpark函數(shù)為線程提供“許可(permit)”,線程調(diào)用park函數(shù)則等待“許可”。這個(gè)有點(diǎn)像信號(hào)量,但是這個(gè)“許可”是不能疊加的,“許可”是一次性的。
比如線程B連續(xù)調(diào)用了三次unpark函數(shù),當(dāng)線程A調(diào)用park函數(shù)就使用掉這個(gè)“許可”,如果線程A再次調(diào)用park,則進(jìn)入等待狀態(tài)。
注意,unpark函數(shù)可以先于park調(diào)用。比如線程B調(diào)用unpark函數(shù),給線程A發(fā)了一個(gè)“許可”,那么當(dāng)線程A調(diào)用park時(shí),它發(fā)現(xiàn)已經(jīng)有“許可”了,那么它會(huì)馬上再繼續(xù)運(yùn)行。
實(shí)際上,park函數(shù)即使沒(méi)有“許可”,有時(shí)也會(huì)無(wú)理由地返回,這點(diǎn)等下再解析。
park和unpark的靈活之處
上面已經(jīng)提到,unpark函數(shù)可以先于park調(diào)用,這個(gè)正是它們的靈活之處。
一個(gè)線程它有可能在別的線程unPark之前,或者之后,或者同時(shí)調(diào)用了park,那么因?yàn)閜ark的特性,它可以不用擔(dān)心自己的park的時(shí)序問(wèn)題,否則,如果park必須要在unpark之前,那么給編程帶來(lái)很大的麻煩!!
考慮一下,兩個(gè)線程同步,要如何處理?
在Java5里是用wait/notify/notifyAll來(lái)同步的。wait/notify機(jī)制有個(gè)很蛋疼的地方是,比如線程B要用notify通知線程A,那么線程B要確保線程A已經(jīng)在wait調(diào)用上等待了,否則線程A可能永遠(yuǎn)都在等待。編程的時(shí)候就會(huì)很蛋疼。
另外,是調(diào)用notify,還是notifyAll?
notify只會(huì)喚醒一個(gè)線程,如果錯(cuò)誤地有兩個(gè)線程在同一個(gè)對(duì)象上wait等待,那么又悲劇了。為了安全起見(jiàn),貌似只能調(diào)用notifyAll了。
park/unpark模型真正解耦了線程之間的同步,線程之間不再需要一個(gè)Object或者其它變量來(lái)存儲(chǔ)狀態(tài),不再需要關(guān)心對(duì)方的狀態(tài)。
?
HotSpot里park/unpark的實(shí)現(xiàn)
每個(gè)java線程都有一個(gè)Parker實(shí)例,Parker類(lèi)是這樣定義的:
?
[cpp]?view plaincopy可以看到Parker類(lèi)實(shí)際上用Posix的mutex,condition來(lái)實(shí)現(xiàn)的。
?
在Parker類(lèi)里的_counter字段,就是用來(lái)記錄所謂的“許可”的。
當(dāng)調(diào)用park時(shí),先嘗試直接能否直接拿到“許可”,即_counter>0時(shí),如果成功,則把_counter設(shè)置為0,并返回:
?
[cpp]?view plaincopy?
?
如果不成功,則構(gòu)造一個(gè)ThreadBlockInVM,然后檢查_(kāi)counter是不是>0,如果是,則把_counter設(shè)置為0,unlock mutex并返回:
?
[cpp]?view plaincopy?
否則,再判斷等待的時(shí)間,然后再調(diào)用pthread_cond_wait函數(shù)等待,如果等待返回,則把_counter設(shè)置為0,unlock mutex并返回:
?
[cpp]?view plaincopy當(dāng)unpark時(shí),則簡(jiǎn)單多了,直接設(shè)置_counter為1,再u(mài)nlock mutext返回。如果_counter之前的值是0,則還要調(diào)用pthread_cond_signal喚醒在park中等待的線程:
?
?
[cpp]?view plaincopy 簡(jiǎn)而言之,是用mutex和condition保護(hù)了一個(gè)_counter的變量,當(dāng)park時(shí),這個(gè)變量置為了0,當(dāng)unpark時(shí),這個(gè)變量置為1。
值得注意的是在park函數(shù)里,調(diào)用pthread_cond_wait時(shí),并沒(méi)有用while來(lái)判斷,所以posix condition里的"Spurious wakeup"一樣會(huì)傳遞到上層Java的代碼里。
?
關(guān)于"Spurious wakeup",參考上一篇blog:http://blog.csdn.net/hengyunabc/article/details/27969613
?
[cpp]?view plaincopy?
這也就是為什么Java dos里提到,當(dāng)下面三種情況下park函數(shù)會(huì)返回:
?
- Some other thread invokes unpark with the current thread as the target; or
- Some other thread interrupts the current thread; or
- The call spuriously (that is, for no reason) returns.
?
相關(guān)的實(shí)現(xiàn)代碼在:
http://hg.openjdk.java.net/build-infra/jdk7/hotspot/file/52c4a1ae6adc/src/share/vm/runtime/park.hpp
http://hg.openjdk.java.net/build-infra/jdk7/hotspot/file/52c4a1ae6adc/src/share/vm/runtime/park.cpp
http://hg.openjdk.java.net/build-infra/jdk7/hotspot/file/52c4a1ae6adc/src/os/linux/vm/os_linux.hpp
http://hg.openjdk.java.net/build-infra/jdk7/hotspot/file/52c4a1ae6adc/src/os/linux/vm/os_linux.cpp ?
其它的一些東東:
Parker類(lèi)在分配內(nèi)存時(shí),使用了一個(gè)技巧,重載了new函數(shù)來(lái)實(shí)現(xiàn)了cache line對(duì)齊。
?
[cpp]?view plaincopyParker里使用了一個(gè)無(wú)鎖的隊(duì)列在分配釋放Parker實(shí)例:
?
?
[cpp]?view plaincopy?
?
總結(jié)與扯談
JUC(Java Util Concurrency)僅用簡(jiǎn)單的park, unpark和CAS指令就實(shí)現(xiàn)了各種高級(jí)同步數(shù)據(jù)結(jié)構(gòu),而且效率很高,令人驚嘆。
轉(zhuǎn)載于:https://www.cnblogs.com/bendantuohai/p/4653543.html
總結(jié)
以上是生活随笔為你收集整理的Java的LockSupport.park()实现分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Linux中常用的操作指令(随时更新)
- 下一篇: ExtJs 4.x Ajax简单封装