前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ReentrantLock中的NonfairSync加锁流程

ReentrantLock中的NonfairSync加锁流程

作者头像
None_Ling
发布2018-10-24 14:55:04
6710
发布2018-10-24 14:55:04
举报
文章被收录于专栏:Android相关Android相关

NonfairSync

重入锁中的非公平锁,尝试获取锁的线程有可能会成功,如果不成功的话,则会进入AQS的队列中。

NonfairSync加锁流程

  1. ReentrantLock.lock函数中,会调用到NonfairSync.lock方法,首先会通过CAS方法,尝试将当前的AQS中的State字段改成从0改成1,如果修改成功的话,说明原来的状态是0,并没有线程占用锁,而且成功的获取了锁,只需要调用setExclusiveOwnerThread函将当前线程设置成持有锁的线程即可。否则,CAS操作失败之后,和普通锁一样,调用acquire(1)函数尝试获取锁。 /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
  2. 而在acquire(1)函数中,会判断tryAcquire(1)以及acquireQueued(addWaiter(Node.EXCLUSIVE), arg),如果尝试获取失败并且添加队列成功的话,那么就会调用selfInterrupt函数中断线程执行,说明已经加入到了AQS的队列中。 在NonfairSync的tryAcquire中,会调用到nonfairTryAcquire函数。
代码语言:javascript
复制
    /**
     * Performs non-fair tryLock.  tryAcquire is
     * implemented in subclasses, but both need nonfair
     * try for trylock method.
     */
    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;
    }

在nonfairTryAcquire函数中,会尝试让当前线程去获取锁:

  1. 获取当前线程,以及AQS的状态
  2. 如果当前AQS的状态为0的话,那么说明当前的锁没有被任何线程获取,则尝试做一次CAS操作,将当前的状态设置成acquires,如果设置成功了的话,那么则将当前线程设置成锁持有的线程,并且返回true,表示获取成功。
  3. 如果当前的状态不为0的话,说明已经有线程持有锁,则判断当前线程与持有锁的线程是否相同,如果相同的话,则将当前的状态加上acquires重新将状态设置,并且返回true,这也就是重入锁的原因。
  4. 如果当前线程没有获取到锁的话,那么就会返回false,表示获取锁失败

而在addWaiter方法则会新建一个Node,然后将节点添加到队列中,让这个节点成为 tail。

代码语言:javascript
复制
 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;
}
  1. 根据Mode新建一个Node对象,而在上面传进来的Mode则是Node.EXCLUSIVE,然后得到尾节点tail,判断当前的尾节点是否为空,如果尾节点不为空的话,那么则将当前节点的prev设置成tail,也就是将自己作为尾节点添加
  2. 然后通过CAS操作,判断尾节点是否有修改过,如果当前的尾节点没有变化过,并且将node成功设置成尾节点的话,那么则将之前尾节点的next设置成当前的node,并且返回尾节点node
  3. 否则调用enq(node)将当前节点添加到队列尾部,并且返回node,而在enq方法中,则会判断头节点和尾节点是否初始化,如果没有初始化则会初始化,然后通过自旋的方式,将tail的next设置成node,并且将node的prev设置成tail,然后将node设置成tail。以达到将node设置成尾节点的目的

接着调用acquireQueued函数,传入尾节点。

代码语言:javascript
复制
 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);
    }
}

在acquireQueued()中,当前线程会等待它在“CLH队列”中前面的所有线程执行并释放锁之后,才能获取锁并返回。如果“当前线程”在休眠等待过程中被中断过,则调用selfInterrupt()来自己产生一个中断。

  1. 进入这个方法后,可以看到会进入一个死循环,这个死循环中,只有当p == head && tryAcquire(arg)才会返回,这个条件代表的是,只有当当前节点的前驱是头节点,并且已经成功获取锁了,才会将当前的节点设置成头节点,并且前节点的next设置成空帮助GC回收,并且将failed标记成失败,并且返回当前线程是否被中断了。
  2. 如果当前节点的前驱不是头节点的话,那么则会判断当前线程在获取锁失败后, 是否需要阻塞,如果需要阻塞的话,就会调用parkAndCheckInterrupt方法进行当前线程的阻塞,并且在线程唤醒后,返回是否当前线程已经中断。

而在sholdParkAfterFailedAcquire函数中,会判断当前节点的前驱的状态,如果当前前驱的状态为Node.SIGNAL的话,那么说明,当前持有锁的线程正在阻塞,需要等它释放了锁之后才能获取,所以返回true,表示需要等待锁的释放,阻塞请求线程。否则,如果持有锁的线程的状态>0的话,说明前驱节点已经处于CANCEL状态,那么就会进入一个循环,直到找到一个状态小于0的(也就是SINGAL,CONDITION,PROPAGETE)状态的节点,然后把该节点的next设置成当前节点,中间的那些CANCEL节点就都被抛弃掉了,如果是PROPAGETE状态的话,那么说明需要一个信号,但是先不阻塞当前线程,调用者会继续尝试获取锁,于是就通过一个CAS操作,将前驱节点的waitStatus设置成Node.SIGNAL,并且告知当前线程不用阻塞。

代码语言:javascript
复制
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016.07.05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • NonfairSync
  • NonfairSync加锁流程
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档