并发编程-(3)-LockSupport

死神---井上织姬

目录:

  • 1、LockSupport工具定位:
  • 2、LockSupport工具定义:
  • 3、LockSupport常用方法:
    • 3.1、阻塞方法
    • 3.2、唤醒线程
    • 3.3、demo分析blocker
    • 3.4、分析LockSupport.parkNanos(long)、LockSupport.parkUnitl(long):
  • 4 、源码分析:
    • 4.1、park()分析
    • 4.2、unpark()分析

1、LockSupport工具定位

      构建同步组件的基础工具,协助AQS完成相应线程的阻塞或者唤醒的工作。

2、LockSupport工具定义

      LockSupport定义了一组以park开头的方法来阻塞当前线程,unpark来唤醒被阻塞的线程。

3、LockSupport常用方法

3.1、阻塞方法
  • 1、void park():阻塞当前线程。如果调用unpark方法或者当前线程被中断,可以从park()方法中返回;Thread.interrupte()详解一下?<<<<<<传送门
  • 2、void park(Object blocker):功能同方法1,入参增加一个Object对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查;
  • 3、void parkNanos(long nanos):阻塞当前线程,最长不超过nanos纳秒,增加了超时返回的特性;
  • 4、void parkNanos(Object blocker, long nanos):功能同方法3,入参增加一个Object对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查;
  • 5、void parkUntil(long deadline):阻塞当前线程,知道deadline(绝对时间);
  • 6、void parkUntil(Object blocker, long deadline):功能同方法5,入参增加一个Object对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查;
3.2、唤醒线程
  • 1、void unpark(Thread thread):唤醒处于阻塞状态的指定线程;
3.3、demo分析blocker:
/**
 * @program: jvmproject
 * @description: LockSupport场景和方法
 * @author: biudefu
 * @create: 2019-08-24
 **/
public class LockSupportMain {

    private static final Object u = new Object();

    public static void main(String[] args) throws InterruptedException {

        System.out.println("main线程 启动!");

        Thread t1 = new Thread(() -> {
            new LockSupportMain().blockCurrencyThread();
        }, "LockSupport_T1");
        t1.start();

        Thread.sleep(3 * 1000L);

        Thread t2 = new Thread(() -> {
            new LockSupportMain().blockCurrencyThreadByObject(u);
        }, "LockSupport_T2");
        t2.start();
        Thread.sleep(3*1000);

        System.out.println("main线程 退出!");

    }

    public void blockCurrencyThreadByObject(Object obj) {
        System.out.println("LockSupport.park() ---> 阻塞当前线程!");
        LockSupport.park(obj);
        System.out.println("LockSupport.park() <--- 被唤醒!");
    }

    public void blockCurrencyThread() {
        System.out.println("Thread name : " + Thread.currentThread().getName() + " call LockSupport.park() ---> 阻塞当前线程!");
        LockSupport.park();
        System.out.println("Thread name : " + Thread.currentThread().getName() + " call LockSupport.park() <--- 被唤醒!");
    }

}

"LockSupport_T2" #12 prio=5 os_prio=31 tid=0x00007fe92e86c000 nid=0x5703 waiting on condition [0x0000700006b93000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x000000076aca4428> (a java.lang.Object)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at com.lyc.jvm.concurrent.locksupport.LockSupportMain.blockCurrencyThreadByObject(LockSupportMain.java:63)
    at com.lyc.jvm.concurrent.locksupport.LockSupportMain.lambda$main$1(LockSupportMain.java:30)
    at com.lyc.jvm.concurrent.locksupport.LockSupportMain$$Lambda$2/793589513.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
    - None

"LockSupport_T1" #11 prio=5 os_prio=31 tid=0x00007fe92e018000 nid=0x5603 waiting on condition [0x0000700006a90000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
    at com.lyc.jvm.concurrent.locksupport.LockSupportMain.blockCurrencyThread(LockSupportMain.java:69)
    at com.lyc.jvm.concurrent.locksupport.LockSupportMain.lambda$main$0(LockSupportMain.java:20)
    at com.lyc.jvm.concurrent.locksupport.LockSupportMain$$Lambda$1/1915910607.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
    - None

在jstack中可以看到- parking to wait for <0x000000076aca4428> (a java.lang.Object),方便进行问题排查。

3.4、分析LockSupport.parkNanos(long)、LockSupport.parkUnitl(long):
public static void main(String[] args) throws InterruptedException {

        System.out.println("main线程 启动!");

        //@003 LockSupport.parkNanos(long)与LockSupport.parkUntil(long);
        Thread t3 = new Thread(()->{
            blockUseNanos(10*1000*1000*1000l);
        },"LockSupport_T3_Nanos");
        Thread t4 = new Thread(()->{
            DateFormat dateFormat2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date myDate2 = dateFormat2.parse("2019-08-26 06:43:00");
                blockUseUntil(myDate2.getTime());
            } catch (ParseException e) {
                e.printStackTrace();
            }

        },"LockSupport_T3_Until");
        t3.start();
        t4.start();

        System.out.println("main线程 退出!");

    }

    private static void blockUseNanos(long l) {
        System.out.println(DateUtil.getNowYYMMDDHHMMSS()+"LockSupport.parkNanos(long) ---> 阻塞当前线程!times:"+(l/1000/1000/1000)+"s");
        LockSupport.parkNanos(l);
        System.out.println(DateUtil.getNowYYMMDDHHMMSS()+"LockSupport.parkNanos(long) <--- 被唤醒!");
    }

    private static void blockUseUntil(long l) {
        System.out.println(DateUtil.getNowYYMMDDHHMMSS()+"LockSupport.parkUntil(long) ---> 阻塞当前线程!唤醒时间:" + DateUtil.timesConvertDate(l));
        LockSupport.parkUntil(l);
        System.out.println(DateUtil.getNowYYMMDDHHMMSS()+"LockSupport.parkUntil(long) <--- 被唤醒!");
    }
main线程 启动!
main线程 退出!
2019-08-26 06:48:49,LockSupport.parkNanos(long) ---> 阻塞当前线程!times:10s
2019-08-26 06:48:49,LockSupport.parkUntil(long) ---> 阻塞当前线程!唤醒时间:2019-08-26 06:49:00,
2019-08-26 06:48:59,LockSupport.parkNanos(long) <--- 被唤醒!
2019-08-26 06:49:00,LockSupport.parkUntil(long) <--- 被唤醒!

4、LockSupport依赖底层sun.misc.Unsafe

4.1、Unsafe.park和Unsafe.unpark的底层实现原理:

Unsafe类中函数基本都是Native属性, 在虚拟机源代码/hotspot/src/share/vm/prims/unsafe.cppUnsafe类Native与c++语言函数之间对应关系:

// These are the methods for 1.8.0
static JNINativeMethod methods_18[] = {
    {CC"getObject",        CC"("OBJ"J)"OBJ"",   FN_PTR(Unsafe_GetObject)},
    {CC"putObject",        CC"("OBJ"J"OBJ")V",  FN_PTR(Unsafe_SetObject)},
    {CC"getObjectVolatile",CC"("OBJ"J)"OBJ"",   FN_PTR(Unsafe_GetObjectVolatile)},
    {CC"putObjectVolatile",CC"("OBJ"J"OBJ")V",  FN_PTR(Unsafe_SetObjectVolatile)},

    DECLARE_GETSETOOP(Boolean, Z),
    DECLARE_GETSETOOP(Byte, B),
    DECLARE_GETSETOOP(Short, S),
    DECLARE_GETSETOOP(Char, C),
    DECLARE_GETSETOOP(Int, I),
    DECLARE_GETSETOOP(Long, J),
    DECLARE_GETSETOOP(Float, F),
    DECLARE_GETSETOOP(Double, D),

    DECLARE_GETSETNATIVE(Byte, B),
    DECLARE_GETSETNATIVE(Short, S),
    DECLARE_GETSETNATIVE(Char, C),
    DECLARE_GETSETNATIVE(Int, I),
    DECLARE_GETSETNATIVE(Long, J),
    DECLARE_GETSETNATIVE(Float, F),
    DECLARE_GETSETNATIVE(Double, D),

    {CC"getAddress",         CC"("ADR")"ADR,             FN_PTR(Unsafe_GetNativeAddress)},
    {CC"putAddress",         CC"("ADR""ADR")V",          FN_PTR(Unsafe_SetNativeAddress)},

    {CC"allocateMemory",     CC"(J)"ADR,                 FN_PTR(Unsafe_AllocateMemory)},
    {CC"reallocateMemory",   CC"("ADR"J)"ADR,            FN_PTR(Unsafe_ReallocateMemory)},
    {CC"freeMemory",         CC"("ADR")V",               FN_PTR(Unsafe_FreeMemory)},

    {CC"objectFieldOffset",  CC"("FLD")J",               FN_PTR(Unsafe_ObjectFieldOffset)},
    {CC"staticFieldOffset",  CC"("FLD")J",               FN_PTR(Unsafe_StaticFieldOffset)},
    {CC"staticFieldBase",    CC"("FLD")"OBJ,             FN_PTR(Unsafe_StaticFieldBaseFromField)},
    {CC"ensureClassInitialized",CC"("CLS")V",            FN_PTR(Unsafe_EnsureClassInitialized)},
    {CC"arrayBaseOffset",    CC"("CLS")I",               FN_PTR(Unsafe_ArrayBaseOffset)},
    {CC"arrayIndexScale",    CC"("CLS")I",               FN_PTR(Unsafe_ArrayIndexScale)},
    {CC"addressSize",        CC"()I",                    FN_PTR(Unsafe_AddressSize)},
    {CC"pageSize",           CC"()I",                    FN_PTR(Unsafe_PageSize)},

    {CC"defineClass",        CC"("DC_Args")"CLS,         FN_PTR(Unsafe_DefineClass)},
    {CC"allocateInstance",   CC"("CLS")"OBJ,             FN_PTR(Unsafe_AllocateInstance)},
    {CC"monitorEnter",       CC"("OBJ")V",               FN_PTR(Unsafe_MonitorEnter)},
    {CC"monitorExit",        CC"("OBJ")V",               FN_PTR(Unsafe_MonitorExit)},
    {CC"tryMonitorEnter",    CC"("OBJ")Z",               FN_PTR(Unsafe_TryMonitorEnter)},
    {CC"throwException",     CC"("THR")V",               FN_PTR(Unsafe_ThrowException)},
    {CC"compareAndSwapObject", CC"("OBJ"J"OBJ""OBJ")Z",  FN_PTR(Unsafe_CompareAndSwapObject)},
    {CC"compareAndSwapInt",  CC"("OBJ"J""I""I"")Z",      FN_PTR(Unsafe_CompareAndSwapInt)},
    {CC"compareAndSwapLong", CC"("OBJ"J""J""J"")Z",      FN_PTR(Unsafe_CompareAndSwapLong)},
    {CC"putOrderedObject",   CC"("OBJ"J"OBJ")V",         FN_PTR(Unsafe_SetOrderedObject)},
    {CC"putOrderedInt",      CC"("OBJ"JI)V",             FN_PTR(Unsafe_SetOrderedInt)},
    {CC"putOrderedLong",     CC"("OBJ"JJ)V",             FN_PTR(Unsafe_SetOrderedLong)},
    {CC"park",               CC"(ZJ)V",                  FN_PTR(Unsafe_Park)},
    {CC"unpark",             CC"("OBJ")V",               FN_PTR(Unsafe_Unpark)}
};

其中:park----->Unsafe_Park,unpark----->Unsafe_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 */
  if (event.should_commit()) {
    oop obj = thread->current_park_blocker();
    event.set_klass((obj != NULL) ? obj->klass() : NULL);
    event.set_timeout(time);
    event.set_address((obj != NULL) ? (TYPE_ADDRESS) cast_from_oop(obj) : 0);
    event.commit();
  }
UNSAFE_END

调用关系:thread->parker()->park(isAbsolute != 0, time)方法。
查看下thread和Parker关系/hotspot/src/share/vm/prims/runtime/thread.hpp(L1740)

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

可以看出, 每个thread类中都包含一个Parker。
Parker定义在/hotspot/src/share/vm/runtime/park.hpp(L56), 定义如下:

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 ;

};

Parker类实际上在Linux系统下是用Posix线程库pthread中的mutex(互斥量),condition(条件变量)来实现的,mutex和condition保护了一个_counter的变量,当park时,这个变量被设置为0,当unpark时,这个变量被设置为1。

4.1、分析park()过程:

以Linux系统实现为例:

void Parker::park(bool isAbsolute, jlong time) {
  // Ideally we'd do something useful while spinning, such
  // as calling unpackTime().
  // Optional fast-path check:
  // Return immediately if a permit is available.
  // We depend on Atomic::xchg() having full barrier semantics
  // since we are doing a lock-free update to _counter.
   //这里通过原子操作来完成_counter清零操作。 若_counter之前>0, 那么说明之前该线程被unpark()过, 就可以直接返回而不被阻塞。
  if (Atomic::xchg(0, &_counter) > 0) return;
  Thread* thread = Thread::current();
  assert(thread->is_Java_thread(), "Must be JavaThread");  //判断一定的是java线程
  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;
  //如果是按参数小于0,或者绝对时间,那么可以直接返回
  if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all
    return;
  }
   //如果时间大于0,判断阻塞超时时间或阻塞截止日期,同时将时间赋值给absTime
  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
  //再次检查, 如果有中断信号。直接返回; 或者申请互斥锁失败,则直接返回pthread_mutex_trylock返回0。任何其他返回值都表示错误。
  //函数pthread_mutex_trylock是POSIX 线程pthread_mutex_lock的非阻塞版本。
  if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
    return;
  }
  //此时已经通过_mutex将该代码进行了互斥操作, 那么直接对_counter都是安全的
  int status ;
  如果count>0, 说明之前原子操作赋值为0没有成功。 而_counter> 0, 线程可以直接不阻塞而返回
  if (_counter > 0)  { // no wait needed
     //将_counter直接清零
    _counter = 0;
    //释放锁并返回, 返回0代表释放锁成功
    status = pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant") ; //这里会去检查一下是否成功了
    // Paranoia to ensure our locked and lock-free paths interact
    // correctly with each other and Java-level accesses.
    OrderAccess::fence(); //这个函数是HotSpot VM对JMM的内存屏障一个具体的实现函数;
    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()
  assert(_cur_index == -1, "invariant");
     //若没有超时时间,那么本线程将进入睡眠状态并释放cpu、释放对_mutex的锁定,等待其他线程调用pthread_cond_signal唤醒该线程;唤醒后会获取对_mutex的锁定的锁定
  if (time == 0) {
    _cur_index = REL_INDEX; // arbitrary choice when not timed
    status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;
  } else {
    _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
     //开始真正的阻塞,超时等待,或者其他线程pthread_cond_signal唤醒该线程
    status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;
    if (status != 0 && WorkAroundNPTLTimedWaitHang) {
      pthread_cond_destroy (&_cond[_cur_index]) ;
      pthread_cond_init    (&_cond[_cur_index], isAbsolute ? NULL : os::Linux::condAttr());
    }
  }
  _cur_index = -1;
  assert_status(status == 0 || status == EINTR ||
                status == ETIME || status == ETIMEDOUT,
                status, "cond_timedwait");
#ifdef ASSERT
  pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
#endif
    //该线程被唤醒了, 同时也对_mutex加锁了, 置位_counter是线程安全的
  _counter = 0 ;
  //解锁_mutex
  status = pthread_mutex_unlock(_mutex) ;
  assert_status(status == 0, status, "invariant") ;
  // Paranoia to ensure our locked and lock-free paths interact
  // correctly with each other and Java-level accesses.
  OrderAccess::fence(); //内存屏障
  // If externally suspended while waiting, re-suspend
  if (jt->handle_special_suspend_equivalent_condition()) {
    jt->java_suspend_self();
  }
}

Parker::park主要做了如下事情:

  • 检查_counter>0(别的线程调用过unpark), 则原子操作清零。线程不用睡眠并返回。
  • 检查该线程是否有中断信号, 有的话,清掉并返回。
  • 尝试通过pthread_mutex_trylock对_mutex加锁来达到线程互斥。
  • 检查_counter是否>0, 若成立,说明第一步原子清零操作失败。检查park是否设置超时时间, 若设置了通过safe_cond_timedwait进行超时等待; 若没有设置,调用pthread_cond_wait进行阻塞等待。 这两个函数都在阻塞等待时都会放弃cpu的使用。 直到别的线程调用pthread_cond_signal唤醒
  • 直接_counter=0清零。
  • 通过pthread_mutex_unlock释放mutex的加锁。
    需要了解下: safe_cond_timedwait/pthread_cond_wait在执行之前肯定已经获取了锁_mutex, 在睡眠前释放了锁, 在被唤醒之前, 首先再取唤醒锁。
4.3、unpark()过程:
void Parker::unpark() {
  int s, status ;
  //首先是互斥获取锁
  status = pthread_mutex_lock(_mutex);
  assert (status == 0, "invariant") ;
  s = _counter;
  //只要把这个状态置为1就行了,就是说多次调用unpack()没啥意义
  _counter = 1;
   //s只能为0,说明没有人调用unpark
  if (s < 1) {
    // thread might be parked
    if (_cur_index != -1) {
      // thread is definitely parked
      //线程已经处于parker状态了
      if (WorkAroundNPTLTimedWaitHang) {
       //pthread_cond_signal可以唤醒pthread_cond_wait()被&_cond[_cur_index]阻塞的线程
        status = pthread_cond_signal (&_cond[_cur_index]);
        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[_cur_index]);
        assert (status == 0, "invariant");
      }
    } else {
    //仅仅解锁
      pthread_mutex_unlock(_mutex);
      assert (status == 0, "invariant") ;
    }
  } else {
    pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant") ;
  }
}

unpark()主要做了如下事情:

  • 首先获取锁_mutex。
  • 对_counter置为1, 而不管之前什么值, 这里说明无论多少函数调用unpark(), 都是无效的, 只会记录一次。
  • 检查线程是否已经被阻塞了, 若已经阻塞了,调用pthread_cond_signal唤醒唤醒。
  • 释放对_mutex的锁定。

参考资料:

《深入理解Java虚拟机-2nd》
《Java 并发编程实战》
《Java 并发编程的艺术》

你可能感兴趣的:(并发编程-(3)-LockSupport)