LockSupport多看一点

写上一篇博客ReentrantLock相关的时候翻代码发现,线程启停的控制在jdk7中使用的是LockSupport实现的,于是忍不住想看下代码,然后愈发不可收拾,Locksupport借助的是POSIX线程的mutex和condition实现的线程间的启停控制。

先放下资源吧,我写博客的习惯就是好东西先放出来

IBM的同仁写的,IBM出品还是相当保障的,浅显易懂的把生涩的东西表达出来,很明了,其实这个部分看完POSIX的知识基本就有了,接下来翻下LockSupport的底层C++实现,具体看下如何使用POSIX实现的线程启停控制。

https://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/index.html

https://www.ibm.com/developerworks/cn/linux/thread/posix_thread2/index.html

https://www.ibm.com/developerworks/cn/linux/thread/posix_thread3/index.html

幸亏翻了,posix的这三篇IBM的系列文章,帮助很大,再来看LockSupport的底层代码生涩度就降低很多了。

LockSupport.java 里

    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        unsafe.park(false, 0L);
        setBlocker(t, null);
    }

setBlocker,设置线程对象的parkBlocker属性,实际在ReentrantLock中,指的是FariSync或者NofairSync 锁。

继续到unsafe.park(false, 0L),这个类就都不陌生了吧,go 

public native void park(boolean var1, long var2);

进入原声方法调用:unsafe.cpp

UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time))
  UnsafeWrapper("Unsafe_Park");
  HS_DTRACE_PROBE3(hotspot, thread__park__begin, thread->parker(), (int) isAbsolute, time);
  JavaThreadParkedState jtps(thread, time != 0);
  thread->parker()->park(isAbsolute != 0, time);
  HS_DTRACE_PROBE1(hotspot, thread__park__end, thread->parker());
UNSAFE_END

这里已经有些看不懂了,因为对java native的调用机制看过一点,没太深入这里就不聊了,进入代码两个java代码层过来的参数 jboolean isAbsolute, jlong time,

宏方法就不看了(老实说c++基本已经还给大学老师了),核心就是这句

thread->parker()->park(isAbsolute != 0, time);

然后来屡下c++里的结构。

class Parker : public os::PlatformParker {
private:
  volatile int _counter ;
  Parker * FreeNext ;
  JavaThread * AssociatedWith ; // Current association

public:
  Parker() : PlatformParker() {
    _counter       = 0 ;
    FreeNext       = NULL ;
    AssociatedWith = NULL ;
  }
protected:
  ~Parker() { ShouldNotReachHere(); }
public:
  // For simplicity of interface with Java, all forms of park (indefinite,
  // relative, and absolute) are multiplexed into one call.
  void park(bool isAbsolute, jlong time);
  void unpark();

  // Lifecycle operators
  static Parker * Allocate (JavaThread * t) ;
  static void Release (Parker * e) ;
private:
  static Parker * volatile FreeList ;
  static volatile int ListLock ;

};

看到实际上继承了PlatformParker,构造方法也会出发父类构造方法来看PlatformParker的结构和构造方法

class PlatformParker : public CHeapObj {
  protected:
    pthread_mutex_t _mutex [1] ;
    pthread_cond_t  _cond  [1] ;

  // 注释析构函数
 
  public:
    PlatformParker() {
      int status;
      status = pthread_cond_init (_cond, NULL);
      assert_status(status == 0, status, "cond_init");
      status = pthread_mutex_init (_mutex, NULL);
      assert_status(status == 0, status, "mutex_init");
    }
} ;

可以看到,使用的正是 POSIX线程的mutex锁和cond条件,构造函数里主要就是做了下锁和条件变量的初始化。

再来看thread.hpp的parker()方法。

  // JSR166 per-thread parker
private:
  Parker*    _parker;
public:
  Parker*     parker() { return _parker; }

可以看到,parker()方法其实就是返回了_parker属性。

那么来到重点Parker类。

parker.hpp

private:
  volatile int _counter ;
  Parker * FreeNext ;
  JavaThread * AssociatedWith ; // Current association
  // 省略其他

看下声明了一个volatile变量_counter,是一个许可的数量,跟ReentrantLock 里定义的许可变量基本都是一个原理。

接下来看代码就能明白它的作用了,park和unpack方法在os_linux.cpp文件中查看。

void Parker::park(bool isAbsolute, jlong time) {
  // Optional fast-path check:
  // Return immediately if a permit is available.
  if (_counter > 0) {
      _counter = 0 ;
      OrderAccess::fence();
      return ;
  }

  Thread* thread = Thread::current();
  assert(thread->is_Java_thread(), "Must be JavaThread");
  JavaThread *jt = (JavaThread *)thread;

  // Optional optimization -- avoid state transitions if there's an interrupt pending.
  // Check interrupt before trying to wait
  if (Thread::is_interrupted(thread, false)) {
    return;
  }

  // Next, demultiplex/decode time arguments
  timespec absTime;
  if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all
    return;
  }
  if (time > 0) {
    unpackTime(&absTime, isAbsolute, time);
  }


  // Enter safepoint region
  // Beware of deadlocks such as 6317397.
  // The per-thread Parker:: mutex is a classic leaf-lock.
  // In particular a thread must never block on the Threads_lock while
  // holding the Parker:: mutex.  If safepoints are pending both the
  // the ThreadBlockInVM() CTOR and DTOR may grab Threads_lock.
  ThreadBlockInVM tbivm(jt);

  // Don't wait if cannot get lock since interference arises from
  // unblocking.  Also. check interrupt before trying wait
  if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
    return;
  }

  int status ;
  if (_counter > 0)  { // no wait needed
    _counter = 0;
    status = pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant") ;
    OrderAccess::fence();
    return;
  }

#ifdef ASSERT
  // Don't catch signals while blocked; let the running threads have the signals.
  // (This allows a debugger to break into the running thread.)
  sigset_t oldsigs;
  sigset_t* allowdebug_blocked = os::Linux::allowdebug_blocked_signals();
  pthread_sigmask(SIG_BLOCK, allowdebug_blocked, &oldsigs);
#endif

  OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
  jt->set_suspend_equivalent();
  // cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()

  if (time == 0) {
    status = pthread_cond_wait (_cond, _mutex) ;
  } else {
    status = os::Linux::safe_cond_timedwait (_cond, _mutex, &absTime) ;
    if (status != 0 && WorkAroundNPTLTimedWaitHang) {
      pthread_cond_destroy (_cond) ;
      pthread_cond_init    (_cond, NULL);
    }
  }
  assert_status(status == 0 || status == EINTR ||
                status == ETIME || status == ETIMEDOUT,
                status, "cond_timedwait");

#ifdef ASSERT
  pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
#endif

  _counter = 0 ;
  status = pthread_mutex_unlock(_mutex) ;
  assert_status(status == 0, status, "invariant") ;
  // If externally suspended while waiting, re-suspend
  if (jt->handle_special_suspend_equivalent_condition()) {
    jt->java_suspend_self();
  }

  OrderAccess::fence();
}

如果有可用的许可,也就是_counter>0,那么直接返回,不需要等待。

然后有一句:

if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
    return;
  }

这句注释上写的也有,如果能获取锁才继续往下走,如果都获取不到锁,那么直接返回。前提也会检查线程是否是阻塞状态。翻译过来就是:当前线程必须是中断状态,并且能够加锁成功,那么继续往下走,否则直接返回。

这里卡了一下,为毛必须是阻塞状态呀,阻塞状态怎么可能代码执行到这里?

呵呵,基础果然是太差呀,查了下。is_interrupted判断是否是阻塞状态而已,也只是个状态,只是线程的一个状态,线程收到这个状态是否要阻塞自己就不一定了,因此状态时阻塞的状态不代表线程就不能继续执行。至于为什么要是阻塞状态才能继续,暂时还不知道继续往下看...

紧接着继续查看许可,如果许可>0,那么直接解锁返回。如果不是那么因为LockSupport.lock(false, 0L),时间是0,就进入

status = pthread_cond_wait (_cond, _mutex) ;

这段代码,看懂了IBM的那三个链接的文章就会知道,POSIX线程执行pthread_cond_wait,之前必须获取mutex锁,因此上边先执行pthread_mutex_trylock 获取锁,然后才能执行到这里,然后就是理解pthread_cond_wiat了,这里就会造成线程休眠,等待条件唤醒,休眠前会解锁mutex,这样别的线程才能继续操作,然后等待条件满足后,其他线程唤醒这里pthread_cond_wait返回前会再次获取锁,获取锁成功后才会返回。也就是说这里就进入休眠状态了,实现了LockSupport.park的语义了。

然后我们再来看LockSupport.unpark的代码:

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") ;
  }
}

相对就简单很多了,设置许可值_counter=1,然后解锁如果许可是0,那么再唤醒下休眠线程。

这样LockSupport.park和unpark的基本代码逻辑就理完了。

讲真不知道这样看源码是不是浪费时间,希望越看越深入,理解能越好吧。加油。

原文地址:https://www.cnblogs.com/aquariusm/p/9267480.html