关闭

Java 线程同步基础类 LockSupport 解析

标签: java线程源码
259人阅读 评论(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
查看评论

LockSupport源码分析(JDK 1.7)

LockSupport是用来创建锁和其他同步类的基本线程阻塞基本体(primitives)。通过调用LockSupport的park方法可以申请一个许可,如果当前许可可用的话,那么则立即返回,否则则阻塞等待许可。直到另外一个线程调用unpark方法对被阻塞的线程的许可进行释放。(默认许可阻塞)par...
  • wojiaolinaaa
  • wojiaolinaaa
  • 2015-11-24 17:59
  • 1003

java并发包系列---LockSupport

长久以来对线程阻塞与唤醒经常我们会使用object的wait和notify,除了这种方式,java并发包还提供了另外一种方式对线程进行挂起和恢复,它就是并发包子包locks提供的LockSupport。 LockSupport提供了park和unpark进行线程的挂起和恢复操作,来看一个简单挂...
  • opensure
  • opensure
  • 2016-11-26 11:35
  • 1398

Java并发工具类LockSupport

LockSupportJ.U.C框架中有一个叫做LockSupport的类,可以精准地阻塞和唤醒特定的线程,并作为其他同步类的原语。 LockSupport包含一个park(Object blocker)和unpark(Object blocker)方法,分别用于阻塞和唤醒。举个例子,下面的代码包...
  • xiangshimoni
  • xiangshimoni
  • 2015-09-24 13:32
  • 2277

【Java并发编程实战】—– AQS(三):阻塞、唤醒:LockSupport

在上篇博客(【Java并发编程实战】—– AQS(二):获取锁、释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起;在释放锁后,需要唤醒该线程的继任节点lock方法,在调用acquireQueued():if (shouldParkAfterFailedAcq...
  • chenssy
  • chenssy
  • 2015-12-27 11:28
  • 8293

Java的LockSupport.park()实现分析

LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了基本的线程同步原语。LockSupport实际上是调用了Unsafe类里的函数,归结到Unsafe里,只有两个函数: public native void unpark(Thread jthread); pub...
  • hengyunabc
  • hengyunabc
  • 2014-06-03 02:52
  • 20280

Java基础之常用类详解

Java中有很多常用类,这些是我们必须要掌握的,下面对这些常用类及其常用方法进行总结并保持更新。包装类我们都知道,Java中有一个系统类型由两部分组成,包含基本类型(primitive),例如int、float、double等,和引用类型(reference type),如String和List。而...
  • u012483425
  • u012483425
  • 2015-05-27 21:11
  • 958

java LockSupport

LockSupport类有着wait() ,notify()类似的功能,不过更精准。        LockSupport.park(Thread thread) ,//阻塞 thread ...
  • fenghuangdesire
  • fenghuangdesire
  • 2014-07-05 08:27
  • 733

LockSupport解析与使用

LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞,实现的阻塞和解除阻塞是基于”许可(permit)”作为关联,permit相当于一个信号量(0,1),默认是0. 线程之间不再需要一个Object或者其它变量来存储状态,不再需要关心对方的状态.
  • SecSF
  • SecSF
  • 2017-11-17 13:51
  • 146

Java基础类库简介

Java以基础类库JFC(Java Foundation Class)的形式为程序员提供编程接口API,类库中的类按照用途归属于不同的包中。(一)java.lang包   Java最常用的包都属于该包,程序不需要注入此包,就可以使用该包中的类,利用这些类可以设计最基本的Java程序。  String...
  • zzp16
  • zzp16
  • 2010-05-22 17:11
  • 7403

LockSupport的park和unpark的基本使用,以及对线程中断的响应性

LockSupport是JDK中比较底层的
  • aitangyong
  • aitangyong
  • 2014-08-04 22:26
  • 6684
    个人资料
    • 访问:604292次
    • 积分:7751
    • 等级:
    • 排名:第3263名
    • 原创:192篇
    • 转载:6篇
    • 译文:0篇
    • 评论:99条
    博客专栏
    文章分类
    最新评论