Java如何实现线程堵塞

概述

Java中的锁,object的wait、sleep都能够堵塞线程,它们到底是如何实现的呢?

源码分析

Java中的可重入锁都是通过LockSupport调用Unsafe的park、unpark方法实现的:

UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time))
  UnsafeWrapper("Unsafe_Park");
  EventThreadPark event;
#ifndef USDT2
  HS_DTRACE_PROBE3(hotspot, thread__park__begin, thread->parker(), (int) isAbsolute, time);
#else /* USDT2 */
   HOTSPOT_THREAD_PARK_BEGIN(
                             (uintptr_t) thread->parker(), (int) isAbsolute, time);
#endif /* USDT2 */
  JavaThreadParkedState jtps(thread, time != 0);
  thread->parker()->park(isAbsolute != 0, time);
#ifndef USDT2
  HS_DTRACE_PROBE1(hotspot, thread__park__end, thread->parker());
#else /* USDT2 */
  HOTSPOT_THREAD_PARK_END(
                          (uintptr_t) thread->parker());
#endif /* USDT2 */
  oop obj = thread->current_park_blocker();
  if (event.should_commit()) {
    event.set_klass(obj ? obj->klass() : (klassOop)NULL);
    event.set_timeout(time);
    event.set_address(obj ? (TYPE_ADDRESS) (uintptr_t) obj : 0);
    event.commit();
  }
UNSAFE_END

UNSAFE_ENTRY(void, Unsafe_Unpark(JNIEnv *env, jobject unsafe, jobject jthread))
  UnsafeWrapper("Unsafe_Unpark");
  Parker* p = NULL;
  if (jthread != NULL) {
    oop java_thread = JNIHandles::resolve_non_null(jthread);
    if (java_thread != NULL) {
      jlong lp = java_lang_Thread::park_event(java_thread);
      if (lp != 0) {
        // This cast is OK even though the jlong might have been read
        // non-atomically on 32bit systems, since there, one word will
        // always be zero anyway and the value set is always the same
        p = (Parker*)addr_from_java(lp);
      } else {
        // Grab lock if apparently null or using older version of library
        MutexLocker mu(Threads_lock);
        java_thread = JNIHandles::resolve_non_null(jthread);
        if (java_thread != NULL) {
          JavaThread* thr = java_lang_Thread::thread(java_thread);
          if (thr != NULL) {
            p = thr->parker();
            if (p != NULL) { // Bind to Java thread for next time.
              java_lang_Thread::set_park_event(java_thread, addr_to_java(p));
            }
          }
        }
      }
    }
  }
  if (p != NULL) {
#ifndef USDT2
    HS_DTRACE_PROBE1(hotspot, thread__unpark, p);
#else /* USDT2 */
    HOTSPOT_THREAD_UNPARK(
                          (uintptr_t) p);
#endif /* USDT2 */
    p->unpark();
  }
UNSAFE_END

可以看到底层都是通过调用thread->parker()实现的,而根据之前Thread的源码分析,Thread.sleep也是采用类似方法实现的;

Pakrer对象

在JavaThread中定义了成员变量:

private:
  Parker*    _parker;
public:
  Parker*     parker() { return _parker; }

void JavaThread::initialize() {
  // Initialize fields
  _parker = Parker::Allocate(this) ;
}

这里的_parker是Parker实例,继承自os::PlatformParker ,其park方法实现如下:

void os::PlatformEvent::park() { 
//_Event是个int变量,如果CAS更新成功,即成功将_Event减1,则退出循环
  int v ;
  for (;;) {
      v = _Event ;
      if (Atomic::cmpxchg (v-1, &_Event, v) == v) break ;
  }
  guarantee (v >= 0, "invariant") ;
 //v=_Event,v=0表示无许可,则需要堵塞等待获得许可;
  if (v == 0) {
     int status = pthread_mutex_lock(_mutex);//获取锁
     assert_status(status == 0, status, "mutex_lock");
     guarantee (_nParked == 0, "invariant") ;
     ++ _nParked ;
    //等待许可,调用pthread_cond_wait进行等待
     while (_Event < 0) {
       //pthread_cond_wait会加入等待队列,同时释放_mutex锁,
      //等待signal方法唤醒,唤醒之后需要重新获取_mutex锁,方法才能返回
        status = pthread_cond_wait(_cond, _mutex);
        if (status == ETIME) { status = EINTR; }
        assert_status(status == 0 || status == EINTR, status, "cond_wait");
     }
     -- _nParked ;
     //获取许可之后,设置可用许可数为0;由此可见许可数最大为1
    _Event = 0 ;
     status = pthread_mutex_unlock(_mutex);//释放锁
     assert_status(status == 0, status, "mutex_unlock");
    OrderAccess::fence();//内存屏障语句
  }
  guarantee (_Event >= 0, "invariant") ;
}
void os::PlatformEvent::unpark() {
 //使用原子方法xchg将1放入寄存器,与_Event所指的内容交换,即_Event=1,然后返回_Event原先的值,
 //如果返回值大于等于0,表示有许可有用,方法直接返回;
  if (Atomic::xchg(1, &_Event) >= 0) return;
 //如果原来的_Event小于0,说明有park方法进入了pthread_cond_wait堵塞
  int status = pthread_mutex_lock(_mutex);//获取锁
  assert_status(status == 0, status, "mutex_lock");
  int AnyWaiters = _nParked;
  assert(AnyWaiters == 0 || AnyWaiters == 1, "invariant");
   //NPTL存在瑕疵,当pthread_cond_timedwait() 方法时间参数为过去时间,
   //会导致_cond变量被破坏或者线程被hang住;
  if (AnyWaiters != 0 && WorkAroundNPTLTimedWaitHang) {
    AnyWaiters = 0;
    //调用signal唤醒pthread_cond_wait调用线程,不判断方法执行结果
    pthread_cond_signal(_cond);
  }
  //然后释放_mutex锁
  status = pthread_mutex_unlock(_mutex);
  assert_status(status == 0, status, "mutex_unlock");
  if (AnyWaiters != 0) {
   // //调用signal唤醒pthread_cond_wait调用线程,并判断方法执行结果
    status = pthread_cond_signal(_cond);
    assert_status(status == 0, status, "cond_signal");
  }
}

这个地方要说明下,如果WorkAroundNPTLTimedWaitHang=true,会先调用signal,然后释放锁;如果WorkAroundNPTLTimedWaitHang=false,会先释放锁,然后调用signal;

  • 先signal,后释放锁 :signal之后,等待线程可以马上运行,但由于无法获取锁,会马上进入waiting状态;
  • 先释放锁,后signal:释放锁之后,如果有等待线程,可能pthread_cond_signal还没运行就发生了线程切换;在极少的情况下,由于pthread_cond_wait有spurious wakeup(伪唤醒)问题,可能导致park方法提前返回,这个时候使用者需要判断cond的状态,再次调用park;

关于WorkAroundNPTLTimedWaitHang还是有些疑问,后续继续探讨;

Atomic方法

在上面的源码中,使用到了Atomic的xchg和cmpxchg方法,这两个方法是采用汇编实现的:

汇编方法的语法如下:

asm ( assembler template  
    : output operands                   (optional)  
    : input operands                    (optional)  
    : clobbered registers list          (optional)  
    );  

output operands和inpupt operands指定参数,它们从左到右依次排列,用','分割,编号从0开始;

inline jint     Atomic::xchg    (jint     exchange_value, volatile jint*     dest) {
  __asm__ volatile (  "xchgl (%2),%0"
                    : "=r" (exchange_value)
                    : "0" (exchange_value), "r" (dest)
                    : "memory");
  return exchange_value;
}

将exchange_value(%0)的值放入通用寄存器,与dest(%2)所指的内容进行交换,返回dest指针原指向内容的值;

  • %0为exchange_value,%1为exchange_value,%2为dest;
  • "r"表示将dest的值读到一个通用寄存器;
  • "0"表示和%0使用同样的通用寄存器,此处表示将exchange_value值读入通用寄存器;
  • "=r"表示将结果写入到exchange_value,而且要使用通用寄存器,由于通用寄存器的内容已经被设置为dest所指向的内容,因此exchange_value等于原dest所指向的内容;
  • asm指示编译器在此插入汇编语句;
  • volatile告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化,即原原本本按原来的样子处理这里的汇编;
  • memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。
int mp = os::is_MP();
  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;
static inline bool is_MP() {
    assert(_processor_count > 0, "invalid processor count");
    return _processor_count > 1;
  }
  • mp表示是否属于多核cpu环境,如果是则LOCK_IF_MP会插入lock指令;
  • %0为exchange_value,%1为exchange_value,%2为compare_value,%3为dest,%4为mp;
  • "a" (compare_value)表示将compare_value读入eax寄存器,与%3(dest)进行比较,如果相等则将%1(exchange_value)写入dest;
  • "=a" (exchange_value)表示将eax寄存器的内容写入到exchange_value;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,884评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,212评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,351评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,412评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,438评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,127评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,714评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,636评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,173评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,264评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,402评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,073评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,763评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,253评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,382评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,749评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,403评论 2 358

推荐阅读更多精彩内容

  • 原文作者 Sandeep.S英文原文 [https://www.ibiblio.org/gferg/ldp/GCC...
    JeffreyLi阅读 40,115评论 8 41
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,685评论 18 139
  • 引用自多线程编程指南应用程序里面多个线程的存在引发了多个执行线程安全访问资源的潜在问题。两个线程同时修改同一资源有...
    Mitchell阅读 1,996评论 1 7
  • 原文: GCC-Inline-Assembly-HOWTO 1. 简介(Introduction.) 1.1 Co...
    桂糊涂阅读 4,541评论 1 5
  • 现在淘宝客、微信营销号、微信公众号、线上店铺商家都有尝试邮件推广。现在几乎都是人手一个QQ,上班还是玩电脑一般都先...
    皮皮虾时尚阅读 2,103评论 0 0