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