美文网首页java锁java多线程
java源码 - ReentrantLock之NonfairSy

java源码 - ReentrantLock之NonfairSy

作者: 晴天哥_王志 | 来源:发表于2018-08-29 23:30 被阅读0次

    开篇

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

    相关文章

      网友评论

        本文标题:java源码 - ReentrantLock之NonfairSy

        本文链接:https://www.haomeiwen.com/subject/ylehwftx.html