LockSupport源码阅读与分析

LockSupport 和 CAS 是Java并发包中很多并发工具实现并发操作的基础,它们底层其实都是依赖Unsafe类实现,下面看下其源码

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

    // Hotspot implementation via intrinsics API
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long parkBlockerOffset;

    static {
        try {
            parkBlockerOffset = unsafe.objectFieldOffset
                (java.lang.Thread.class.getDeclaredField("parkBlocker"));
        } catch (Exception ex) { throw new Error(ex); }
    }

首先,LockSupport类是不能够被用户程序实例化的,该类有两个成员变量,其中unsafe是java提供的底层操作类,对于parkBlockerOffset,还记得Thread类有个成员叫parkBlocker吗,它表示线程阻塞在哪个对象上,这里取其相对与Thread的偏移地址,方便后续操作。那么这里有个问题,为什么不使用set,get方法进行访问呢?试想如果线程处于阻塞状态,此时要获取这个parkBlocker对象,使用set,get方法,线程是没有办法响应的!所以只能获取到地址,然后获取对象.接下来我们主要看下常用到的两个方法,park和unpark.


1,park方法

public static void park() {
        unsafe.park(false, 0L);
    }
unsafe的park是一个native方法,其源码在unsafe的Unsafe_Park方法中,主要是下面这一句:

thread->parker()->park(isAbsolute != 0, time);
isAbsolute就是上面传过来的false,这里我们暂时不知道这个参数是干啥的,继续看。还记得之前Thread源码阅读的时候,在jvm的Thread类中有个成员
// JSR166 per-thread parker
private:
  Parker*    _parker;
public:
  Parker*     parker() { return _parker; }
之前在阅读Object源码以及Thread源码时都提到用到了park,当时没有仔细看这里的park是如何实现的,下面好好看下这个Parker类,每个线程都有对应的一个Parker实例,其定义如下:

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 ;

};

class PlatformParker : public CHeapObj<mtInternal> {
  protected:
    enum {
      REL_INDEX = 0,
      ABS_INDEX = 1
    };
    int _cur_index; // which cond is in use: -1, 0, 1
    pthread_mutex_t _mutex [1] ;
    pthread_cond_t _cond [2] ; // one for relative times and one for abs.

  public:       // TODO-FIXME: make dtor private
    ~PlatformParker() { guarantee (0, "invariant") ; }

  public:
    PlatformParker() {
      int status;
      status = pthread_cond_init (&_cond[REL_INDEX], os::Linux::condAttr());
      assert_status(status == 0, status, "cond_init rel");
      status = pthread_cond_init (&_cond[ABS_INDEX], NULL);
      assert_status(status == 0, status, "cond_init abs");
      status = pthread_mutex_init (_mutex, NULL);
      assert_status(status == 0, status, "mutex_init");
      _cur_index = -1; // mark as unused
    }
};
可以看到Parker类继承自PlatformParker,父类中有两个成员互斥锁,和线程同步条件变量,在构造函数中进行初始化,可以看到Parker实际上是通过posix的mutex,condition实现的.再看下子类Parker,他实际上是一个链表,有成员_counter,这里我们暂时不知道这个_counter是干啥的,继续看,还有一个成员表示当前线程,还有个FreeList,和ListLock,这里我们暂时也不知道是干啥的.我们先来看下park方法干了些啥,
if (Atomic::xchg(0, &_counter) > 0) return;
这里xchg实际上是执行一个汇编指令xchg,作用是将_counter的值替换为0,如果此时_counter>0,那么park函数立即返回。哦,这个_counter就是LockSupport中注释里说的"permit",许可,park如果发现当前已经有许可,那么就立即返回.接着看,

Thread* thread = Thread::current();
  assert(thread->is_Java_thread(), "Must be JavaThread");
  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;
  }

如果当前线程已经被中断,立即返回,
timespec absTime;
  if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all
    return;
  }
  if (time > 0) {
    unpackTime(&absTime, isAbsolute, time);
  }

这里我们知道了isAbsolute是什么意思了,即所谓的绝对时间,那什么是绝对时间?源码中注释如下:
/*
The passed in time value is either a relative time in nanoseconds or an absolute time in milliseconds.
*/
我理解这里所说的是精度问题,再往下看,
// Don't wait if cannot get lock since interference arises from
  // unblocking.  Also. check interrupt before trying wait
  if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
    return;
  }

如果无法获取到锁,立即返回
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;
  }

如果此时_counter被修改了,值大于0,解锁,立即返回
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");
  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());
    }
  }
  _cur_index = -1;

首先设置osThread状态为CONDVAR_WAIT,如果time为0,则调用pthread_cond_wait一直等待条件被触发,否则调用pthread_cond_timedwait进行等待

_counter = 0 ;
  status = pthread_mutex_unlock(_mutex) ;

等待结束后,设置_counter为0,释放锁,以上就是park的执行逻辑.


2,unpark方法

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") ;
  }
}

首先加锁,因为要访问互斥量,然后设置_counter=1,如果_counter设置之前的值大于等于1,通知等待的条件变量,否则,直接释放锁,然后返回.


从上我们可以看到,park和unpark主要借助于linux线程相关的以下几个函数实现:

pthread_mutex_lock

pthread_mutex_unlock

pthread_cond_wait

pthread_cond_timedwait

pthread_cond_signal

阅读源码的过程中发现还有个类ParkEvent也提供了park/unpark功能,那他们有啥区别吗?源码中有这样一段注释

// The base-class, PlatformEvent, is platform-specific while the ParkEvent is
// platform-independent.  PlatformEvent provides park(), unpark(), etc., and
// is abstract -- that is, a PlatformEvent should never be instantiated except
// as part of a ParkEvent.
// Equivalently we could have defined a platform-independent base-class that
// exported Allocate(), Release(), etc.  The platform-specific class would extend
// that base-class, adding park(), unpark(), etc.
//
// A word of caution: The JVM uses 2 very similar constructs:
// 1. ParkEvent are used for Java-level "monitor" synchronization.
// 2. Parkers are used by JSR166-JUC park-unpark.
//
// We'll want to eventually merge these redundant facilities and use ParkEvent.
意思是说ParkEvent是用于java级别的synchronize关键字,Parker是JSR166来的并发工具集合,后面会统一使用ParkEvent,我们看下ParkEvent类,

class ParkEvent : public os::PlatformEvent {
  private:
    ParkEvent * FreeNext ;

    // Current association
    Thread * AssociatedWith ;
    intptr_t RawThreadIdentity ;        // LWPID etc
    volatile int Incarnation ;

    // diagnostic : keep track of last thread to wake this thread.
    // this is useful for construction of dependency graphs.
    void * LastWaker ;

  public:
    // MCS-CLH list linkage and Native Mutex/Monitor
    ParkEvent * volatile ListNext ;
    ParkEvent * volatile ListPrev ;
    volatile intptr_t OnList ;
    volatile int TState ;
    volatile int Notified ;             // for native monitor construct
    volatile int IsWaiting ;            // Enqueued on WaitSet


  private:
    static ParkEvent * volatile FreeList ;
    static volatile int ListLock ;

    // It's prudent to mark the dtor as "private"
    // ensuring that it's not visible outside the package.
    // Unfortunately gcc warns about such usage, so
    // we revert to the less desirable "protected" visibility.
    // The other compilers accept private dtors.

  protected:        // Ensure dtor is never invoked
    ~ParkEvent() { guarantee (0, "invariant") ; }

    ParkEvent() : PlatformEvent() {
       AssociatedWith = NULL ;
       FreeNext       = NULL ;
       ListNext       = NULL ;
       ListPrev       = NULL ;
       OnList         = 0 ;
       TState         = 0 ;
       Notified       = 0 ;
       IsWaiting      = 0 ;
    }

    // We use placement-new to force ParkEvent instances to be
    // aligned on 256-byte address boundaries.  This ensures that the least
    // significant byte of a ParkEvent address is always 0.

    void * operator new (size_t sz) ;
    void operator delete (void * a) ;

  public:
    static ParkEvent * Allocate (Thread * t) ;
    static void Release (ParkEvent * e) ;
} ;

它继承了PlatformEvent类
class PlatformEvent : public CHeapObj<mtInternal> {
  private:
    double CachePad [4] ;   // increase odds that _mutex is sole occupant of cache line
    volatile int _Event ;
    volatile int _nParked ;
    pthread_mutex_t _mutex  [1] ;
    pthread_cond_t  _cond   [1] ;
    double PostPad  [2] ;
    Thread * _Assoc ;

  public:       // TODO-FIXME: make dtor private
    ~PlatformEvent() { guarantee (0, "invariant") ; }

  public:
    PlatformEvent() {
      int status;
      status = pthread_cond_init (_cond, os::Linux::condAttr());
      assert_status(status == 0, status, "cond_init");
      status = pthread_mutex_init (_mutex, NULL);
      assert_status(status == 0, status, "mutex_init");
      _Event   = 0 ;
      _nParked = 0 ;
      _Assoc   = NULL ;
    }

    // Use caution with reset() and fired() -- they may require MEMBARs
    void reset() { _Event = 0 ; }
    int  fired() { return _Event; }
    void park () ;
    void unpark () ;
    int  TryPark () ;
    int  park (jlong millis) ; // relative timed-wait only
    void SetAssociation (Thread * a) { _Assoc = a ; }
} ;

在PlatformEvent中定义了park,unpark方法,我们首先看下在parkEvent中park方法的实现,
int v ;
  for (;;) {
      v = _Event ;
      if (Atomic::cmpxchg (v-1, &_Event, v) == v) break ;
  }

这里的_Event与上面的_counter类似,如果cmxchg成功,相当与是消耗掉一个许可,那么跳出循环,接着
if (v == 0) {
     // Do this the hard way by blocking ...
     int status = pthread_mutex_lock(_mutex);
     assert_status(status == 0, status, "mutex_lock");
     guarantee (_nParked == 0, "invariant") ;
     ++ _nParked ;
     while (_Event < 0) {
        status = pthread_cond_wait(_cond, _mutex);
        // for some reason, under 2.7 lwp_cond_wait() may return ETIME ...
        // Treat this the same as if the wait was interrupted
        if (status == ETIME) { status = EINTR; }
        assert_status(status == 0 || status == EINTR, status, "cond_wait");
     }
     -- _nParked ;

    _Event = 0 ;
     status = pthread_mutex_unlock(_mutex);
     assert_status(status == 0, status, "mutex_unlock");
    // Paranoia to ensure our locked and lock-free paths interact
    // correctly with each other.
    OrderAccess::fence();
  }

如果剩余许可大于0,直接返回,如果等于0,说明此时_Event<=-1,此时进入条件等待,再看下unpark
if (Atomic::xchg(1, &_Event) >= 0) return;
如果已有许可,返回,接着
int status = pthread_mutex_lock(_mutex);
  assert_status(status == 0, status, "mutex_lock");
  int AnyWaiters = _nParked;
  assert(AnyWaiters == 0 || AnyWaiters == 1, "invariant");
  if (AnyWaiters != 0 && WorkAroundNPTLTimedWaitHang) {
    AnyWaiters = 0;
    pthread_cond_signal(_cond);
  }
  status = pthread_mutex_unlock(_mutex);
  assert_status(status == 0, status, "mutex_unlock");
  if (AnyWaiters != 0) {
    status = pthread_cond_signal(_cond);
    assert_status(status == 0, status, "cond_signal");
  }

看下来,这两个类在实现上没有什么不同,但是为什么要两个类呢?再会过头看注释,ParkEvent是用于java级别的synchronize关键字,Parker是JSR166来的并发工具集合,难道是在使用上有不同?我们编写下测试例子:
public class TestSynchronized {
    private static Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        int N = 5;
        List<Thread> threads = new ArrayList<Thread>();
        for(int i = 0; i < N; ++i){
           Thread t = new Thread("TestSynchronized-thread-"+i){
                @Override
                public void run() {
                    synchronized (lock){
                        System.out.println(Thread.currentThread().getName() + " get synchronized lock!");
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
           threads.add(t);
        }
        synchronized(lock){
            for(Thread t : threads){
                t.start();
                Thread.sleep(1000);
            }
        }

        for(Thread t : threads)
            t.join();
    }
}

其结果如下:
TestSynchronized-thread-4 get synchronized lock!
TestSynchronized-thread-3 get synchronized lock!
TestSynchronized-thread-2 get synchronized lock!
TestSynchronized-thread-1 get synchronized lock!
TestSynchronized-thread-0 get synchronized lock!

额,最先启动的线程,最后获取到锁,是不是有点与认知不一样,这里的lock最终底层调用的是ParkEvent.park,再看个例子:
public class ReentrantLockTest {

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

        final ReentrantLock lock = new ReentrantLock();

        List<Thread> threadList = new ArrayList<Thread>();
        int N = 5;

        for(int i=0;i<N;i++){
            Thread t = new Thread("ReentrantLockTest-thread-"+i){
                @Override
                public void run() {
                    try{
                        lock.lock();//底层park实现
                        System.out.println(Thread.currentThread().getName()+" test");
                        Thread.sleep(100);
                    }catch (Exception e){

                    }finally {
                        lock.unlock();
                    }
                }
            };

            threadList.add(t);
        }
        lock.lock();
        for(Thread t : threadList){
            t.start();
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        lock.unlock();//底层unpark实现
        for(Thread t : threadList){
            t.join();
        }
    }
}
执行结果如下:
ReentrantLockTest-thread-0 test
ReentrantLockTest-thread-1 test
ReentrantLockTest-thread-2 test
ReentrantLockTest-thread-3 test
ReentrantLockTest-thread-4 test

这里与正常的认知想符合,先启动先获得锁嘛,这里的lock底层是调用Parker.park实现,我们先来看下lock.lock()都干了啥,

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

这里如果没有获取到锁,会将当前线程封装成一个节点,加入到等待队列末尾,再看下synchrozied的等待对列,还记得Object源码阅读时wait方法中有一行
 AddWaiter (&node) ;//添加到ObjectMonitor的等待队列中_Waitset  
其函数如下
inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
  assert(node != NULL, "should not dequeue NULL node");
  assert(node->_prev == NULL, "node already in list");
  assert(node->_next == NULL, "node already in list");
  // put node at end of queue (circular doubly linked list)
  if (_WaitSet == NULL) {
    _WaitSet = node;
    node->_prev = node;
    node->_next = node;
  } else {
    ObjectWaiter* head = _WaitSet ;
    ObjectWaiter* tail = head->_prev;
    assert(tail->_next == head, "invariant check");
    tail->_next = node;
    head->_prev = node;
    node->_next = head;
    node->_prev = tail;
  }
}

看到问题了吧,这里每个等待节点进来不是像上面那样添加到队列末尾,而是添加到队列的头部,哦,这样就可以解释上面获取锁的顺序了!






猜你喜欢

转载自blog.csdn.net/chengzhang1989/article/details/79669045