LockSupport park和unpark

前言

在上一篇文章线程池返回值Future中,源码分析线程池结果获取阻塞的原因。

LockSupport.unpark(t);

LockSupport.parkNanos(this, nanos);或者LockSupport.park(this);

使用unpark唤醒线程,使用park阻塞线程,下面介绍他们的作用以及与wait和notify或者notifyAll的区别。

1. LockSupport.unpark(t);和LockSupport.park(this);源码解析

1.1 代理类

public class LockSupport {
    private LockSupport() {} // Cannot be instantiated.

    private static void setBlocker(Thread t, Object arg) {
        // Even though volatile, hotspot doesn't need a write barrier here.
        UNSAFE.putObject(t, parkBlockerOffset, arg);
    }

    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

    /**
     * Disables the current thread for thread scheduling purposes unless the
     * permit is available.
     *
     * <p>If the permit is available then it is consumed and the call returns
     * immediately; otherwise
     * the current thread becomes disabled for thread scheduling
     * purposes and lies dormant until one of three things happens:
     *
     * <ul>
     * <li>Some other thread invokes {@link #unpark unpark} with the
     * current thread as the target; or
     *
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread; or
     *
     * <li>The call spuriously (that is, for no reason) returns.
     * </ul>
     *
     * <p>This method does <em>not</em> report which of these caused the
     * method to return. Callers should re-check the conditions which caused
     * the thread to park in the first place. Callers may also determine,
     * for example, the interrupt status of the thread upon return.
     *
     * @param blocker the synchronization object responsible for this
     *        thread parking
     * @since 1.6
     */
    public static void park(Object blocker) {
        //当前线程
        Thread t = Thread.currentThread();
        //设置当前线程的blocker,为参数blocker
        setBlocker(t, blocker);
        //阻塞线程
        UNSAFE.park(false, 0L);
        //线程blocker释放
        setBlocker(t, null);
    }

我们看看 setBlocker(t, blocker);

    private static void setBlocker(Thread t, Object arg) {
        // Even though volatile, hotspot doesn't need a write barrier here.
        UNSAFE.putObject(t, parkBlockerOffset, arg);
    }

 使用UNSAFE对Thread的parkBlockerOffset偏移量的属性设置值。整个操作是原子性的。

//两个很实用的方法
//对object对象的offset偏移量的属性,设置update的值
public native void putObject(Object object, long valueOffset, Object update);

//对object的offset偏移量的属性,此属性的值比对expect主存的值,如果想等就置为update并返回true;否则返回false
public final native boolean compareAndSwapObject(Object object, long valueOffset, Object expect, Object update);

获取偏移量的方法,class字节码获取,比如这里的parkBlockerOffset 

    // Hotspot implementation via intrinsics API
    private static final sun.misc.Unsafe UNSAFE;
    private static final long parkBlockerOffset;
    private static final long SEED;
    private static final long PROBE;
    private static final long SECONDARY;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception ex) { throw new Error(ex); }
    }

源码分析 

LockSupport其实是一个静态代理类,功能实现是用Unsafe类里面的native方法。起源于JDK1.6,JDK1.5只能用wait和notify.

    //isAbsolute是时间的修饰,绝对或者相对,false就是相对时间
    public native void park(boolean isAbsolute, long time);

    public native void unpark(Object thread);

根据API的说明

unpark函数为线程提供“许可(permit)”,线程调用park函数则等待“许可”,但“许可”是一次性的。

比如:线程A连续调用了N(N>1)次unpark函数,当线程B调用park函数就使用掉这个“许可”,如果线程A再次调用一次park,则进入等待状态。如果线程A处于等待许可状态,再次调用park,则会永远等待下去,调用unpark也无法唤醒。

 1.2 park和unpark对比wait/notify/notifyAll

unpark函数能够先于park调用,无时序限制这个非常有用,事先准备,需要线程等待时,如果条件具备可以立即执行。

在JDK5中,是用wait/notify/notifyAll来同步的,必须要wait调用成功才能,才能notify,而且使用notify只能唤醒一个线程,一般使用notifyAll方法。

    /** Only one thread at a time can own an object's monitor.
     *
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of this object's monitor.
     * @see        java.lang.Object#notifyAll()
     * @see        java.lang.Object#wait()
     */
    public final native void notify();

notify:Only one thread at a time can own an object's monitor. 

2. OpenJDK源码分析

由于是UNSAFE 的 native方法,要深入本质分析,必须看源码,下面来看C++源码

Parker的unpark方法

void Parker::unpark() {  
  int s, status ;
  //加锁
  status = os::Solaris::mutex_lock (_mutex) ;
  //断言加锁成功与否
  assert (status == 0, "invariant") ;
  //缓存_counter
  s = _counter;
  //_counter置为1,唤醒状态
  _counter = 1;
  //释放锁
  status = os::Solaris::mutex_unlock (_mutex) ;
  //断言锁释放
  assert (status == 0, "invariant") ;
  if (s < 1) {
    //_counter只有0,1;_counter为0,等待状态才能执行cond_signal唤醒操作;等于1即唤醒状态,无需操作
    status = os::Solaris::cond_signal (_cond) ;
    //断言成功
    assert (status == 0, "invariant") ;
  }
}

可以看出 _counter变量决定线程的等待和唤醒,唤醒状态_counter为1,等待状态_counter为0 

下面看Parker的park方法

void Parker::park(bool isAbsolute, jlong time) {
  //信号量_counter标记是否已经是唤醒状态
  if (_counter > 0) {
      //等待状态
      _counter = 0 ;
      //阻塞线程
      OrderAccess::fence();
      return ;
  }

  //如果已经等待
  //当前线程指针
  Thread* thread = Thread::current();
  assert(thread->is_Java_thread(), "Must be JavaThread");
  JavaThread *jt = (JavaThread *)thread;
  //如果已经是interrupt,没有状态更新的必要
  if (Thread::is_interrupted(thread, false)) {
    return;
  }
  
  timespec absTime;
  //park等待操作超时 don't wait at all
  if (time < 0) { // don't wait at all
    return;
  }
  if (time > 0) {
    //转换时间
    unpackTime(&absTime, isAbsolute, time);
  }

  ThreadBlockInVM tbivm(jt);
  //中断,加锁失败
  if (Thread::is_interrupted(thread, false) ||
      os::Solaris::mutex_trylock(_mutex) != 0) {
    return;
  }
  
  int status ;
  //再次重试
  if (_counter > 0)  { // no wait needed
    //重复第一步操作
    _counter = 0;
    //释放刚刚加的锁
    status = os::Solaris::mutex_unlock(_mutex);
    assert (status == 0, "invariant") ;
    OrderAccess::fence();
    return;
  }

//下面是一些满足条件的编译
//操作cond_wait,cond_timedwait 执行,使线程等待

#ifdef ASSERT
  sigset_t oldsigs;
  sigset_t* allowdebug_blocked = os::Solaris::allowdebug_blocked_signals();
  thr_sigsetmask(SIG_BLOCK, allowdebug_blocked, &oldsigs);
#endif
  OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
  jt->set_suspend_equivalent();

#if defined(__sparc) && defined(COMPILER2)
  if (ClearFPUAtPark) { _mark_fpu_nosave() ; }
#endif
  if (time == 0) {
    status = os::Solaris::cond_wait (_cond, _mutex) ;
  } else {
    status = os::Solaris::cond_timedwait (_cond, _mutex, &absTime);
  }
   assert_status(status == 0 || status == EINTR ||
                status == ETIME || status == ETIMEDOUT,
                status, "cond_timedwait");

#ifdef ASSERT
  thr_sigsetmask(SIG_SETMASK, &oldsigs, NULL);
#endif
  _counter = 0 ;
  status = os::Solaris::mutex_unlock(_mutex);
  assert_status(status == 0, status, "mutex_unlock") ;
  if (jt->handle_special_suspend_equivalent_condition()) {
    jt->java_suspend_self();
  }
  OrderAccess::fence();
}

3. 总结

一个Parker实例对应一个Java的线程,源码分析Parker使用mutex(mutex_trylock,mutex_lock,mutex_unlock),condition(cond_signal ,cond_timedwait,cond_wait )来实现的。

Parker类里的_counter全局属性,记录许可(permit)。

猜你喜欢

转载自blog.csdn.net/fenglllle/article/details/83049251