Java 线程同步基础类 LockSupport 解析

来源:互联网 发布:小夕kitty淘宝 编辑:程序博客网 时间:2024/06/06 20:43

    • 概述
    • 源码解析
    • 灵活的特性
    • 应用
      • AQS AbstractQueuedSynchronizer
      • FutureTask
    • 参考资料

概述

LockSupport 类提供了基本的线程同步原语,是实现 AbstractQueuedSynchronizer 和 ReentrantLock 的基础。

源码解析

LockSupport 最重要的就是加锁的 park、解锁的 unpark 方法了:

    public static void park(Object blocker) {        Thread t = Thread.currentThread();        setBlocker(t, blocker);        unsafe.park(false, 0L);        setBlocker(t, null);    }    private static void setBlocker(Thread t, Object arg) {        UNSAFE.putObject(t, parkBlockerOffset, arg);    }    public static void unpark(Thread thread) {        if (thread != null)            unsafe.unpark(thread);    }    

park 的入参 blocker 是阻塞线程的对象,通过 setBlocker 方法设置到 Thread 对象的 parkBlocker 域,用于线程监控和分析。unpark 的入参是将要解锁的线程。parker 方法的注释里提到了三种情况会造成 park 返回:unpark、中断、spurious wakeup。

Some other thread invokes {@link #unpark unpark} with the current thread as the target.
Some other thread {@linkplain Thread#interrupt interrupts} the current thread.
The call spuriously (that is, for no reason) returns.
关于 spurious wakeup 可以看看参考文献里的资料。

park 和 unpark 方法实际上是调用了 Unsafe 类里的函数:

    // in sun.misc.Unsafe.java    // thread 参数标记要唤醒的线程    public native void unpark(Object thread);    // time 为 0L 时,表示不设置超时时间    // isAbsolute 参数为 true,表示 time 为超时唤醒的绝对时间;否则为相对时间    public native void park(boolean isAbsolute, long time);

从上面的代码可知,park 时可以设置超时时间,对应的 LockSupport 中也有能够设置绝对超时时间的 parkUntil,和相对超时时间的 parkNanos:

    public static void parkUntil(long deadline) {        UNSAFE.park(true, deadline);    }    public static void parkNanos(long nanos) {        if (nanos > 0)            UNSAFE.park(false, nanos);    }    

Unsafe 的 park 和 unpark 又是怎么实现的呢?在底层,每一个 Thread 对象的都有一个 Parker 实例,

  // in vm/runtime/thread.hpp  // JSR166 per-thread parkerprivate:  Parker*    _parker;public:  Parker*     parker() { return _parker; }

Parker 类继承自 PlatformParker,PlatformParker 的实现就是系统相关了,linux 的实现是利用了 POSIX 的 mutex 和 condition。

class Parker : public os::PlatformParker {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();};// in linux/vm/os_linux.hppclass PlatformParker : public CHeapObj<mtInternal> {  protected:    pthread_mutex_t _mutex [1] ;    pthread_cond_t  _cond  [2] ; // one for relative times and one for abs.};

Parker::park 方法中,主要是调用了 pthread_cond_wait 方法和 safe_cond_timedwait,safe_cond_timedwait 调用了 pthread_cond_timedwait。

void Parker::park(bool isAbsolute, jlong time) {  // 这中间省略了很多代码  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;    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());    }  }  // 这里也省略了很多代码  status = pthread_mutex_unlock(_mutex) ;  assert_status(status == 0, status, "invariant") ;  // 内存栅栏,内存屏障  OrderAccess::fence();}int os::Linux::safe_cond_timedwait(pthread_cond_t *_cond, pthread_mutex_t *_mutex, const struct timespec *_abstime){   if (is_NPTL()) {      return pthread_cond_timedwait(_cond, _mutex, _abstime);   } else {      // 6292965: LinuxThreads pthread_cond_timedwait() resets FPU control      // word back to default 64bit precision if condvar is signaled. Java      // wants 53bit precision.  Save and restore current value.      int fpu = get_fpu_control_word();      int status = pthread_cond_timedwait(_cond, _mutex, _abstime);      set_fpu_control_word(fpu);      return status;   }}

Parker::unpark 设置 _counter 为1,再 unlock mutex,如果 _counter 之前小于 1,则调用 pthread_cond_signal 唤醒等待的线程。

void Parker::unpark() {  int s, status ;  status = pthread_mutex_lock(_mutex);  assert (status == 0, "invariant") ;  s = _counter;  _counter = 1;  if (s < 1) {    // thread might be parked    if (_cur_index != -1) {      // thread is definitely parked      if (WorkAroundNPTLTimedWaitHang) {        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") ;  }}

灵活的特性

在 Parker::park 中有这么一段代码,如果 _counter 大于 0,则立即返回。在上面 unpark 的代码中,我们看到 unpark 将 _counter 设置为 1,也就是说:两个线程之间的 park 和 unpark 不存在时序关系,可以先 unpark 再 park,不会造成死锁。这相对于存在依赖关系的 wait/notify 机制是一个巨大的优点。

void Parker::park(bool isAbsolute, jlong time) {  // 这中间省略了很多代码  int status ;  if (_counter > 0)  { // no wait needed    _counter = 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();    return;  }  // 这后面省略了更多代码

应用

AQS: AbstractQueuedSynchronizer

AbstractQueuedSynchronizer 中获取锁的代码如下,这里使用 for 循环,而不是在 park 返回后就立即返回,也是为了排除中断、虚假唤醒等并非因资源可用而唤醒的情况。

    final boolean acquireQueued(final Node node, int arg) {        boolean failed = true;        try {            boolean interrupted = false;            for (;;) {                final Node p = node.predecessor();                if (p == head && tryAcquire(arg)) {                    setHead(node);                    p.next = null; // help GC                    failed = false;                    return interrupted;                }                if (shouldParkAfterFailedAcquire(p, node) &&                    parkAndCheckInterrupt())                    interrupted = true;            }        } finally {            if (failed)                cancelAcquire(node);        }    }

FutureTask

在 FutureTask 中,等待操作完成的 awaitDone 大致分为以下步骤:
1. 先检查是否存在中断,是则抛异常 InterruptedException;
2. 是否已经完成,是则返回;
3. 进入等待队列中;
4. 当设置了超时时间 nanos 时,调用 LockSupport.parkNanos 方法等待;
5. 没有设置超时时间时,调用 LockSupport.park 方法等待。

    private int awaitDone(boolean timed, long nanos)        throws InterruptedException {        final long deadline = timed ? System.nanoTime() + nanos : 0L;        WaitNode q = null;        boolean queued = false;        for (;;) {            if (Thread.interrupted()) {                removeWaiter(q);                throw new InterruptedException();            }            int s = state;            if (s > COMPLETING) {                if (q != null)                    q.thread = null;                return s;            }            else if (s == COMPLETING) // cannot time out yet                Thread.yield();            else if (q == null)                q = new WaitNode();            else if (!queued)                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,                                                     q.next = waiters, q);            else if (timed) {                nanos = deadline - System.nanoTime();                if (nanos <= 0L) {                    removeWaiter(q);                    return state;                }                LockSupport.parkNanos(this, nanos);            }            else                LockSupport.park(this);        }    }

参考资料

Java 的 LockSupport.park() 实现分析
多线程编程中条件变量和虚假唤醒(spurious wakeup)的讨论
Why does pthread_cond_wait have spurious wakeups?

原创粉丝点击