Java的LockSupport.park()实现分析

来源:互联网 发布:美容仪原理 知乎 编辑:程序博客网 时间:2024/05/16 05:40


转自:http://blog.csdn.net/hengyunabc/article/details/28126139


LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了基本的线程同步原语。LockSupport实际上是调用了Unsafe类里的函数,归结到Unsafe里,只有两个函数:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public native void unpark(Thread jthread);  
  2. 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 plain copy
 在CODE上查看代码片派生到我的代码片
  1. class Parker : public os::PlatformParker {  
  2. private:  
  3.   volatile int _counter ;  
  4.   ...  
  5. public:  
  6.   void park(bool isAbsolute, jlong time);  
  7.   void unpark();  
  8.   ...  
  9. }  
  10. class PlatformParker : public CHeapObj<mtInternal> {  
  11.   protected:  
  12.     pthread_mutex_t _mutex [1] ;  
  13.     pthread_cond_t  _cond  [1] ;  
  14.     ...  
  15. }  
可以看到Parker类实际上用Posix的mutex,condition来实现的。

在Parker类里的_counter字段,就是用来记录所谓的“许可”的。

当调用park时,先尝试直接能否直接拿到“许可”,即_counter>0时,如果成功,则把_counter设置为0,并返回:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. void Parker::park(bool isAbsolute, jlong time) {  
  2.   // Ideally we'd do something useful while spinning, such  
  3.   // as calling unpackTime().  
  4.   
  5.   
  6.   // Optional fast-path check:  
  7.   // Return immediately if a permit is available.  
  8.   // We depend on Atomic::xchg() having full barrier semantics  
  9.   // since we are doing a lock-free update to _counter.  
  10.   if (Atomic::xchg(0, &_counter) > 0) return;  

如果不成功,则构造一个ThreadBlockInVM,然后检查_counter是不是>0,如果是,则把_counter设置为0,unlock mutex并返回:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. ThreadBlockInVM tbivm(jt);  
  2. if (_counter > 0)  { // no wait needed  
  3.   _counter = 0;  
  4.   status = pthread_mutex_unlock(_mutex);  

否则,再判断等待的时间,然后再调用pthread_cond_wait函数等待,如果等待返回,则把_counter设置为0,unlock mutex并返回:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. if (time == 0) {  
  2.   status = pthread_cond_wait (_cond, _mutex) ;  
  3. }  
  4. _counter = 0 ;  
  5. status = pthread_mutex_unlock(_mutex) ;  
  6. assert_status(status == 0, status, "invariant") ;  
  7. OrderAccess::fence();  
当unpark时,则简单多了,直接设置_counter为1,再unlock mutext返回。如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. void Parker::unpark() {  
  2.   int s, status ;  
  3.   status = pthread_mutex_lock(_mutex);  
  4.   assert (status == 0, "invariant") ;  
  5.   s = _counter;  
  6.   _counter = 1;  
  7.   if (s < 1) {  
  8.      if (WorkAroundNPTLTimedWaitHang) {  
  9.         status = pthread_cond_signal (_cond) ;  
  10.         assert (status == 0, "invariant") ;  
  11.         status = pthread_mutex_unlock(_mutex);  
  12.         assert (status == 0, "invariant") ;  
  13.      } else {  
  14.         status = pthread_mutex_unlock(_mutex);  
  15.         assert (status == 0, "invariant") ;  
  16.         status = pthread_cond_signal (_cond) ;  
  17.         assert (status == 0, "invariant") ;  
  18.      }  
  19.   } else {  
  20.     pthread_mutex_unlock(_mutex);  
  21.     assert (status == 0, "invariant") ;  
  22.   }  
  23. }  
简而言之,是用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 plain copy
 在CODE上查看代码片派生到我的代码片
  1. if (time == 0) {  
  2.   status = pthread_cond_wait (_cond, _mutex) ;  
  3. }  

这也就是为什么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/jdk7/jdk7/hotspot/file/81d815b05abb/src/share/vm/runtime/park.hpp
http://hg.openjdk.java.Net/jdk7/jdk7/hotspot/file/81d815b05abb/src/share/vm/runtime/park.cpp
http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/81d815b05abb/src/os/Linux/vm/os_linux.hpp
http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/81d815b05abb/src/os/linux/vm/os_linux.cpp

其它的一些东东:

Parker类在分配内存时,使用了一个技巧,重载了new函数来实现了cache line对齐。

[cpp] view plain copy
  1. // We use placement-new to force ParkEvent instances to be  
  2. // aligned on 256-byte address boundaries.  This ensures that the least  
  3. // significant byte of a ParkEvent address is always 0.  
  4.    
  5. void * operator new (size_t sz) ;  
Parker里使用了一个无锁的队列在分配释放Parker实例:

[cpp] view plain copy
  1. volatile int Parker::ListLock = 0 ;  
  2. Parker * volatile Parker::FreeList = NULL ;  
  3.   
  4. Parker * Parker::Allocate (JavaThread * t) {  
  5.   guarantee (t != NULL, "invariant") ;  
  6.   Parker * p ;  
  7.   
  8.   // Start by trying to recycle an existing but unassociated  
  9.   // Parker from the global free list.  
  10.   for (;;) {  
  11.     p = FreeList ;  
  12.     if (p  == NULL) break ;  
  13.     // 1: Detach  
  14.     // Tantamount to p = Swap (&FreeList, NULL)  
  15.     if (Atomic::cmpxchg_ptr (NULL, &FreeList, p) != p) {  
  16.        continue ;  
  17.     }  
  18.   
  19.     // We've detached the list.  The list in-hand is now  
  20.     // local to this thread.   This thread can operate on the  
  21.     // list without risk of interference from other threads.  
  22.     // 2: Extract -- pop the 1st element from the list.  
  23.     Parker * List = p->FreeNext ;  
  24.     if (List == NULL) break ;  
  25.     for (;;) {  
  26.         // 3: Try to reattach the residual list  
  27.         guarantee (List != NULL, "invariant") ;  
  28.         Parker * Arv =  (Parker *) Atomic::cmpxchg_ptr (List, &FreeList, NULL) ;  
  29.         if (Arv == NULL) break ;  
  30.   
  31.         // New nodes arrived.  Try to detach the recent arrivals.  
  32.         if (Atomic::cmpxchg_ptr (NULL, &FreeList, Arv) != Arv) {  
  33.             continue ;  
  34.         }  
  35.         guarantee (Arv != NULL, "invariant") ;  
  36.         // 4: Merge Arv into List  
  37.         Parker * Tail = List ;  
  38.         while (Tail->FreeNext != NULL) Tail = Tail->FreeNext ;  
  39.         Tail->FreeNext = Arv ;  
  40.     }  
  41.     break ;  
  42.   }  
  43.   
  44.   if (p != NULL) {  
  45.     guarantee (p->AssociatedWith == NULL, "invariant") ;  
  46.   } else {  
  47.     // Do this the hard way -- materialize a new Parker..  
  48.     // In rare cases an allocating thread might detach  
  49.     // a long list -- installing null into FreeList --and  
  50.     // then stall.  Another thread calling Allocate() would see  
  51.     // FreeList == null and then invoke the ctor.  In this case we  
  52.     // end up with more Parkers in circulation than we need, but  
  53.     // the race is rare and the outcome is benign.  
  54.     // Ideally, the # of extant Parkers is equal to the  
  55.     // maximum # of threads that existed at any one time.  
  56.     // Because of the race mentioned above, segments of the  
  57.     // freelist can be transiently inaccessible.  At worst  
  58.     // we may end up with the # of Parkers in circulation  
  59.     // slightly above the ideal.  
  60.     p = new Parker() ;  
  61.   }  
  62.   p->AssociatedWith = t ;          // Associate p with t  
  63.   p->FreeNext       = NULL ;  
  64.   return p ;  
  65. }  
  66.   
  67.   
  68. void Parker::Release (Parker * p) {  
  69.   if (p == NULL) return ;  
  70.   guarantee (p->AssociatedWith != NULL, "invariant") ;  
  71.   guarantee (p->FreeNext == NULL      , "invariant") ;  
  72.   p->AssociatedWith = NULL ;  
  73.   for (;;) {  
  74.     // Push p onto FreeList  
  75.     Parker * List = FreeList ;  
  76.     p->FreeNext = List ;  
  77.     if (Atomic::cmpxchg_ptr (p, &FreeList, List) == List) break ;  
  78.   }  
  79. }  

总结与扯谈

JUC(Java Util Concurrency)仅用简单的park, unpark和CAS指令就实现了各种高级同步数据结构,而且效率很高,令人惊叹。

在C++程序员各种自制轮子的时候,Java程序员则有很丰富的并发数据结构,如lock,latch,queue,map等信手拈来。

要知道像C++直到C++11才有标准的线程库,同步原语,但离高级的并发数据结构还有很远。boost库有提供一些线程,同步相关的类,但也是很简单的。Intel的tbb有一些高级的并发数据结构,但是国内boost都用得少,更别说tbb了。

最开始研究无锁算法的是C/C++程序员,但是后来很多Java程序员,或者类库开始自制各种高级的并发数据结构,经常可以看到有分析Java并发包的文章。反而C/C++程序员总是在分析无锁的队列算法。高级的并发数据结构,比如并发的HashMap,没有看到有相关的实现或者分析的文章。在C++11之后,这种情况才有好转。

因为正确高效实现一个Concurrent Hash Map是很困难的,要对内存CPU有深刻的认识,而且还要面对CPU不断升级带来的各种坑。

我认为真正值得信赖的C++并发库,只有Intel的tbb和微软的PPL。

https://software.intel.com/en-us/node/506042     Intel® Threading Building Blocks 

http://msdn.microsoft.com/en-us/library/dd492418.aspx   Parallel Patterns Library (PPL)

另外FaceBook也开源了一个C++的类库,里面也有并发数据结构。

https://github.com/facebook/folly

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 头盔玻璃罩边固定老是掉怎么办 电动车不戴头盔被扣车怎么办 郴州骑电动车没戴头盔怎么办 配置数据源时发现两个版本怎么办 微信占用内存3g怎么办 打印机显示内存已满怎么办 网页显示代理服务器连接失败怎么办 墙内线路断了怎么办 墙里的电线坏了怎么办 鱼竿最前端断了怎么办 下雨天墙壁与管道间漏水怎么办 电饭锅的电线被雨淋了怎么办 钢琴跨八度手短怎么办 弹钢琴手指不灵活怎么办呢? 理发剪不锋利了怎么办 室外宽带线断了怎么办 接宽带光纤太短怎么办 装修光纤网线太短怎么办 宽带入户线断了怎么办 电信有无线没网怎么办 墙里的网线断了怎么办 墙里网线断了怎么办 3根网线断了怎么办 剪了层次的头发怎么办 小米6充电线坏了怎么办 小米6导航信号弱怎么办 麦多多充不了电怎么办 一加数据线坏了怎么办 小米耳机泡水了怎么办 公司拖欠工资公司破产了怎么办 苹果x外壳掉漆怎么办 手机壳按键很硬怎么办 棉质白衣服染色怎么办 白棉t恤混洗染色怎么办 包包被衣服染色了怎么办 白色衣服染了菜汁怎么办 一加3t屏幕刺眼怎么办 怀孕吃了好多杏怎么办 门破了个洞怎么办 钢圈轮毂刮花了怎么办 瓷砖用刀子划了怎么办