Java 线程同步基础类 LockSupport 解析

标签: java线程源码
170人阅读 评论(0) 收藏 举报
分类:

概述

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 parker
private:
  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.hpp
class 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?

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:567629次
    • 积分:7419
    • 等级:
    • 排名:第3266名
    • 原创:189篇
    • 转载:6篇
    • 译文:0篇
    • 评论:97条
    博客专栏
    文章分类
    最新评论