LockSupport類是Java6(JSR166-JUC)引入的一個類,提供了基本的線程同步原語。LockSupport實際上是調用了Unsafe類里的函數,歸結到Unsafe里,只有兩個函數:
?park:阻塞當前線程(Block?current?thread),字面理解park,就算占住,停車的時候不就把這個車位給占住了么?起這個名字還是很形象的。
unpark:?使給定的線程停止阻塞(Unblock?the?given?thread?blocked )。
public?native?void?unpark(Thread?jthread);??public?native?void?park(boolean?isAbsolute,?long?time);?? ?
isAbsolute參數是指明時間是絕對的,還是相對的。
僅僅兩個簡單的接口,就為上層提供了強大的同步原語。
先來解析下兩個函數是做什么的。
unpark函數為線程提供“許可(permit)”,線程調用park函數則等待“許可”。這個有點像信號量,但是這個“許可”是不能疊加的,“許可”是一次性的。
比如線程B連續調用了三次unpark函數,當線程A調用park函數就使用掉這個“許可”,如果線程A再次調用park,則進入等待狀態。
注意,unpark函數可以先于park調用。比如線程B調用unpark函數,給線程A發了一個“許可”,那么當線程A調用park時,它發現已經有“許可”了,那么它會馬上再繼續運行。
實際上,park函數即使沒有“許可”,有時也會無理由地返回,這點等下再解析。
park和unpark的靈活之處
上面已經提到,unpark函數可以先于park調用,這個正是它們的靈活之處。
一個線程它有可能在別的線程unPark之前,或者之后,或者同時調用了park,那么因為park的特性,它可以不用擔心自己的park的時序問題,否則,如果park必須要在unpark之前,那么給編程帶來很大的麻煩!!
考慮一下,兩個線程同步,要如何處理?
在Java5里是用wait/notify/notifyAll來同步的。wait/notify機制有個很蛋疼的地方是,比如線程B要用notify通知線程A,那么線程B要確保線程A已經在wait調用上等待了,否則線程A可能永遠都在等待。編程的時候就會很蛋疼。
另外,是調用notify,還是notifyAll?
notify只會喚醒一個線程,如果錯誤地有兩個線程在同一個對象上wait等待,那么又悲劇了。為了安全起見,貌似只能調用notifyAll了。
park/unpark模型真正解耦了線程之間的同步,線程之間不再需要一個Object或者其它變量來存儲狀態,不再需要關心對方的狀態。
?
HotSpot里park/unpark的實現
每個java線程都有一個Parker實例,Parker類是這樣定義的:
?
[cpp]?view plaincopy
class?Parker?:?public?os::PlatformParker?{??private:????volatile?int?_counter?;????...??public:????void?park(bool?isAbsolute,?jlong?time);????void?unpark();????...??}??class?PlatformParker?:?public?CHeapObj<mtInternal>?{????protected:??????pthread_mutex_t?_mutex?[1]?;??????pthread_cond_t??_cond??[1]?;??????...??}?? 可以看到Parker類實際上用Posix的mutex,condition來實現的。
?
在Parker類里的_counter字段,就是用來記錄所謂的“許可”的。
當調用park時,先嘗試直接能否直接拿到“許可”,即_counter>0時,如果成功,則把_counter設置為0,并返回:
?
[cpp]?view plaincopy
void?Parker::park(bool?isAbsolute,?jlong?time)?{????????????????????if?(Atomic::xchg(0,?&_counter)?>?0)?return;?? ?
?
如果不成功,則構造一個ThreadBlockInVM,然后檢查_counter是不是>0,如果是,則把_counter設置為0,unlock mutex并返回:
?
[cpp]?view plaincopy
ThreadBlockInVM?tbivm(jt);??if?(_counter?>?0)??{???_counter?=?0;????status?=?pthread_mutex_unlock(_mutex);?? ?
否則,再判斷等待的時間,然后再調用pthread_cond_wait函數等待,如果等待返回,則把_counter設置為0,unlock mutex并返回:
?
[cpp]?view plaincopy
if?(time?==?0)?{????status?=?pthread_cond_wait?(_cond,?_mutex)?;??}??_counter?=?0?;??status?=?pthread_mutex_unlock(_mutex)?;??assert_status(status?==?0,?status,?"invariant")?;??OrderAccess::fence();?? 當unpark時,則簡單多了,直接設置_counter為1,再unlock mutext返回。如果_counter之前的值是0,則還要調用pthread_cond_signal喚醒在park中等待的線程:
?
?
[cpp]?view plaincopy
void?Parker::unpark()?{????int?s,?status?;????status?=?pthread_mutex_lock(_mutex);????assert?(status?==?0,?"invariant")?;????s?=?_counter;????_counter?=?1;????if?(s?<?1)?{???????if?(WorkAroundNPTLTimedWaitHang)?{??????????status?=?pthread_cond_signal?(_cond)?;??????????assert?(status?==?0,?"invariant")?;??????????status?=?pthread_mutex_unlock(_mutex);??????????assert?(status?==?0,?"invariant")?;???????}?else?{??????????status?=?pthread_mutex_unlock(_mutex);??????????assert?(status?==?0,?"invariant")?;??????????status?=?pthread_cond_signal?(_cond)?;??????????assert?(status?==?0,?"invariant")?;???????}????}?else?{??????pthread_mutex_unlock(_mutex);??????assert?(status?==?0,?"invariant")?;????}??}?? 簡而言之,是用mutex和condition保護了一個_counter的變量,當park時,這個變量置為了0,當unpark時,這個變量置為1。
值得注意的是在park函數里,調用pthread_cond_wait時,并沒有用while來判斷,所以posix condition里的"Spurious wakeup"一樣會傳遞到上層Java的代碼里。
?
關于"Spurious wakeup",參考上一篇blog:http://blog.csdn.net/hengyunabc/article/details/27969613
?
[cpp]?view plaincopy
if?(time?==?0)?{????status?=?pthread_cond_wait?(_cond,?_mutex)?;??}?? ?
這也就是為什么Java dos里提到,當下面三種情況下park函數會返回:
?
- 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.
?
相關的實現代碼在:
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類在分配內存時,使用了一個技巧,重載了new函數來實現了cache line對齊。
?
[cpp]?view plaincopy
???void?*?operator?new?(size_t?sz)?;?? Parker里使用了一個無鎖的隊列在分配釋放Parker實例:
?
?
[cpp]?view plaincopy
volatile?int?Parker::ListLock?=?0?;??Parker?*?volatile?Parker::FreeList?=?NULL?;????Parker?*?Parker::Allocate?(JavaThread?*?t)?{????guarantee?(t?!=?NULL,?"invariant")?;????Parker?*?p?;??????????for?(;;)?{??????p?=?FreeList?;??????if?(p??==?NULL)?break?;??????????????if?(Atomic::cmpxchg_ptr?(NULL,?&FreeList,?p)?!=?p)?{?????????continue?;??????}????????????????????????Parker?*?List?=?p->FreeNext?;??????if?(List?==?NULL)?break?;??????for?(;;)?{??????????????????guarantee?(List?!=?NULL,?"invariant")?;??????????Parker?*?Arv?=??(Parker?*)?Atomic::cmpxchg_ptr?(List,?&FreeList,?NULL)?;??????????if?(Arv?==?NULL)?break?;????????????????????if?(Atomic::cmpxchg_ptr?(NULL,?&FreeList,?Arv)?!=?Arv)?{??????????????continue?;??????????}??????????guarantee?(Arv?!=?NULL,?"invariant")?;??????????????????Parker?*?Tail?=?List?;??????????while?(Tail->FreeNext?!=?NULL)?Tail?=?Tail->FreeNext?;??????????Tail->FreeNext?=?Arv?;??????}??????break?;????}??????if?(p?!=?NULL)?{??????guarantee?(p->AssociatedWith?==?NULL,?"invariant")?;????}?else?{??????????????????????????????????????????????????????????p?=?new?Parker()?;????}????p->AssociatedWith?=?t?;????????????p->FreeNext???????=?NULL?;????return?p?;??}??????void?Parker::Release?(Parker?*?p)?{????if?(p?==?NULL)?return?;????guarantee?(p->AssociatedWith?!=?NULL,?"invariant")?;????guarantee?(p->FreeNext?==?NULL??????,?"invariant")?;????p->AssociatedWith?=?NULL?;????for?(;;)?{??????????Parker?*?List?=?FreeList?;??????p->FreeNext?=?List?;??????if?(Atomic::cmpxchg_ptr?(p,?&FreeList,?List)?==?List)?break?;????}??}?? ?
?
總結與扯談
JUC(Java Util Concurrency)僅用簡單的park, unpark和CAS指令就實現了各種高級同步數據結構,而且效率很高,令人驚嘆。
轉載于:https://www.cnblogs.com/bendantuohai/p/4653543.html
總結
以上是生活随笔為你收集整理的Java的LockSupport.park()实现分析的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。