您现在的位置是:首页 > 文章详情

java源码 - ReentrantLock之NonfairSync

日期:2018-08-29点击:399

开篇

NonfairSync和FairSync相比而言,多了一次抢占机会,其他处理逻辑几乎是一模一样。


加锁过程

ReentrantLock的的锁过程如下:

  • 1、先尝试获取锁,通过tryAcquire()实现。
  • 2、获取锁失败后,线程被包装成Node对象后添加到CLH队列,通过addWaiter()实现。
  • 3、添加CLH队列后,逐步的去执行CLH队列的线程,如果当前线程获取到了锁,则返回;否则,当前线程进行休眠,直到唤醒并重新获取锁了才返回。
 public void lock() { sync.lock(); } static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } } public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } 

acquire的操作流程

  • 1、第一步通过tryAcquire()尝试获取锁,成功则返回
  • 2、获取锁失败后通过addWaiter添加到CLH队列的末尾
  • 3、添加CLH队列后,通过acquireQueued()方法逐步的去执行CLH队列的线程,如果当前线程获取到了锁则返回;否则当前线程进行休眠,直到唤醒并重新获取锁后返回。
 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } 

tryAcquire的操作流程

  • 1、如果锁未占用的情况下:当前线程直接抢占锁并设置锁占用线程为当前线程,非公平锁NonfairSync和FairSync的差别就在于这个地方,非公平锁直接抢占锁,而公平锁则需要判断是否位于头结点来决定是否抢占。
  • 2、如果锁被占用的情况下:判断当前线程是否是占用锁线程,如果是则实现锁的可重入功能,设置锁占用次数。
  • 3、如果上述全否那么就返回占锁失败的。
 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } 

addWaiter的操作流程

  • 1、将当前线程包装成Node对象。
  • 2、先尝试通过快速失败法尝试在CLH队尾插入Node对象
  • 3、如果快速插入失败后那么就通过enq方法在CLH队尾插入Node对象
 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; } private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } } 

acquireQueued的操作流程

  • 1、如果当前节点Node的前驱节点属于head,当前节点属于老二地位通过tryAcquire()尝试获取锁,获取成功后那么就释放原head节点(可以理解为head已经释放锁然后从CLH删除),把当前节点设置为head节点。
  • 2、通过shouldParkAfterFailedAcquire()方法判断Node代表的线程是否进入waiting状态,直到被unpark()。
  • 3、parkAndCheckInterrupt()方法将当前线程进入waiting状态。
  • 4、休眠线程被唤醒的时候会执行 if (p == head && tryAcquire(arg))逻辑判断
 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); } } 

shouldParkAfterFailedAcquire的操作流程

  • 1、如果前置节点处于SIGNAL状态,那么当前线程进入阻塞状态,返回true
  • 2、如果前置节点处于ws>0也就是取消状态,那么当前线程节点就往前查找第一个状态处于ws<=0的节点
  • 3、如果前置状态ws=0的节点,那么就把前置节点设置为SIGNAL状态
  • 4、整个shouldParkAfterFailedAcquire函数是在for()循环当中循环执行的,我们可以想象按照步骤2->3->1的顺序执行,按照前置遍历寻找合适的前置节点,接着发现前置节点ws状态为0后重新设置为SIGNAL,最后发现前置节点状态为SINGAL后休眠线程自身。
  • 5、线程从运行态进入waiting状态其实也是经历了一系列的处理过程的。
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) return true; if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); } 


解锁过程

release过程

  • 1、通过tryRelease()方法尝试让当前线程释放锁对象
  • 2、通过unparkSuccessor()方法设置当前节点状态ws=0并且唤醒CLH队列中的下一个等待线程
 public void unlock() { sync.release(1); } public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } 

tryRelease过程

  • 1、如果占用锁线程非当前线程直接抛异常
  • 2、递减锁计数后如果值为0那么就释放当前锁占用者
  • 3、更新锁状态为未占用,即state为0
 protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } 

unparkSuccessor过程

  • 1、设置当前Node状态为0
  • 2、寻找下一个等待线程节点来唤醒等待线程并通过LockSupport.unpark()唤醒线程
  • 3、寻找下一个等待线程,如果当前Node的下一个节点符合状态就直接进行唤醒,否则从队尾开始进行倒序查找,找到最优先的线程进行唤醒。
 private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread); } 
原文链接:https://yq.aliyun.com/articles/666312
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章