ReentrantLock中NonfairSync锁机制详解

本文详细探讨了ReentrantLock中的NonfairSync锁机制,从加锁执行流程出发,阐述了在单线程和多线程竞争情况下的锁升级过程。非公平锁在获取锁时直接尝试获取,减少了等待时间,但可能导致锁饥饿。同时分析了释放锁的流程,以及锁的状态判断和更新机制。
摘要由CSDN通过智能技术生成

 

1.加锁的执行流程

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

从上我们可以看到,lock()在获取锁时先执行tryAcquire(arg)方法,因此当仅有一个线程时我们采用偏向锁实现加锁,并且为可重入锁。当有多个线程存在竞争时锁升级为重量级锁。
2.释放锁的流程

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

3.非公平锁(可重入的)

此处可以看到若仅有一个线程仅进行了一次CAS操作,下次在获取锁时,仅对状态进行了判断,并没有进行CAS操作,相当于此时加入了偏向锁。

加锁操作:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {//保证了int c = getState();
                                              //      if (c == 0) {
                                              //          if (compareAndSetState(0, acquires))
                                              //以上三句为原子操作,并且仅在c等于0时才能执行
                                              //setExclusiveOwnerThread(current);语句,从而保证
                                              //当有多个线程竞争时,仅有一个线程抢占锁成功。

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

4.释放锁操作:

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

 

5.重量级锁

//用于并发实现将当前线程的节点成功插入到队尾,并返回当前线程节点
private Node addWaiter(Node mode) {//此方法的作用范围为插入之前tail指向节点的next引用以及插入之后
                                   //tail指向节点的prev引用

    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)) { // 保证Node pred = tail;
                                             //     if (pred != null) {
					     //         node.prev = pred;
                                             //         if (compareAndSetTail(pred, node))
                                             // 以上这三句都为原子操作
                                             // 要么成功将当前线程的node节点插入队列尾部,
                                             // 要不失败(插入不成功)
					     // 但插入不成功并不会使队列处于不一致的无效状态

            pred.next = node;
            return node;
        }
    }
    enq(node);//此函数可以代替上述语句(从Node pred = tail;到当前行上一行为止,即可以没有上述语句
    return node;
}
private Node enq(final Node node) {
    for (;;) {   //无限循环是为了保证在队尾插入节点不成功时可以重复进行,直到插入成功
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))//保证从Node t = tail;到此行都为原子操作,由于仅当
                                              //tail等于null且head等于null时才能执行成功,因此仅
                                              //有一个线程能成功执行tail = head;
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {//保证Node t = tail;
					     //    node.prev = t;
                                             //    if (compareAndSetTail(t, node))这三句为原子操作
                                             //要么成功将当前node插入队列尾部,要不失败(插入不成功)
					     //但插入不成功并不会使队列处于不一致的无效状态


                t.next = node;               //
                return t;                    //我们可以看到只有在插入成功时才会退出循环
            }
        }
    }
}
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)) {//若p == head,可知此线程位于队列的顶端,
                                               //使得被阻塞后线程被释放后可以再一次获得竞争
                                               //锁的权利,由于nonfairTryAcquire()方法
                                               //使得新加入的线程也会先去竞争锁,
                                               //从而可能会造成新加入的线程会先得到锁,
                                               //引起不公平竞争。
                                               //由于每次仅有一个节点处于释放状态,因此可以
                                               //可以保证这段代码仅有一个线程进行操作,因此
                                               //不需要进行同步

                setHead(node);//将当前线程节点置为哨兵节点,即thread为null
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
//用于通过设置当前线程节点的上一个节点的等待状态为Node.SIGNAL来表明此线程处于阻塞状态
//(此时此刻实际并没有阻塞,但是在不久将来肯定会阻塞)
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 {            //这几句用于去除此节点之前的已取消节点,作用范围为此节点到此节点之前的
                        //首个未取消节点,并且会读取这些节点的waitStatus状态,并可能会对首个未取消状
                        //态进行写入

            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.
         */
         //为什么在pred.waitStatus等于0时在将状态设置为Node.SIGNAL不直接返回true?
         //因为每一次仅有一个线程处于被唤醒状态(是指经过release(int arg)方法释放)
         //当被唤醒的线程与新申请锁的线程竞争失败后但并未被阻塞(pred.waitStatus等于0)
         //之前,新申请锁的线程由于head.waitStatus等于0,从而不能进行唤醒阻塞线程的操作
         //若此唤醒的线程不进行再一次的tryAcquire()操作,那么在没有新线程申请锁的情况下
         //将没有线程处于唤醒状态,使得位于阻塞队列中的线程永远不能被唤醒
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;//表示从头节点开始进行释放

        if (h != null && h.waitStatus != 0)//h.waitStatus<0,表示后继节点有处于阻塞的线程,
                                           //因此需要对后继首个阻塞节点进行唤醒
            unparkSuccessor(h);
        return true;
    }
    return false;
}

 

//每次仅有一个线程(或者为获得所得锁的线程或者为被唤醒后中断
//取消的线程)调用unparkSuccessor(Node node)方法,并且在此方法返回之前不会有其它线程调用
//unparkSuccessor(Node node)方法
private void unparkSuccessor(Node node) {

    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)//若在release(int arg)方法中调用,表示除了新申请锁线程外,仅有一个线程处于唤醒状态
               //因为处于唤醒状态的线程若与新申请锁的线程竞争失败或者处于唤醒状态的线程被中断取消
               //(因为处于取消状态的线程也会调用release(int arg)去释放锁,因此取消过程中且在调用
               //release(int arg)方法之前,若新申请锁的线程得到锁后执行完临界区就不需要进行唤醒阻
               //塞线程的操作),从而存在处于唤醒状态的锁在未被阻塞之前,新申请锁的线程得到锁后执行
               //完临界区去调用release(int arg),避免了有两个线程同于处于唤醒状态
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;

    if (s == null || s.waitStatus > 0) {
        s = null;
        //为什么从后往前遍历?
        //我们知道前面在往尾部插入节点时,当节点被成功插入时(可保证原子操作),其插入节点的prev
        //指针已经设置好了,但是其插入节点的prev指向的节点的next指针并没有设置好,若从前往后遍历就会
        //得到无效状态
        //得到队列中离当前节点node节点最近的未取消节点所存储的阻塞线程进行释放
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}
private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    if (node == null)
        return;

    node.thread = null;

    // Skip cancelled predecessors
    Node pred = node.prev;//我们知道此节点必定是除了head节点之外离队列头部最近的其waitStatus<=0的节点
                          //而且在上述shouldParkAfterFailedAcquire(Node pred, Node node)方法中也进行了
                          //前向遍历来将取消状态节点清除出队列,那么这两者之间会不会存在竞争呢?
                          //答案肯定是不会的,因为前向遍历都是遍历到离当前节点最近的waitStatus<=0的节点
                          //停止,由于在此处进行遍历时当前节点的状态node.waitStatus必定<=0,因此进行                          //调用shouldParkAfterFailedAcquire(Node pred, Node node)方法的线程最多只能
                          //影响到当前节点,不会影响此节点之前的节点,从而两者不会存在同步问题
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    // predNext is the apparent node to unsplice. CASes below will
    // fail if not, in which case, we lost race vs another cancel
    // or signal, so no further action is necessary.
    Node predNext = pred.next;

    // Can use unconditional write instead of CAS here.
    // After this atomic step, other Nodes can skip past us.
    // Before, we are free of interference from other threads.
    node.waitStatus = Node.CANCELLED;//当执行完这句话后,若进行切换使得申请锁的线程调用
                                     //shouldParkAfterFailedAcquire(Node pred, Node node)方法进行
                                     //前向遍历时就有可能会直接将此节点从队列中清除
    // If we are the tail, remove ourselves.
    if (node == tail && compareAndSetTail(node, pred)) {//若当前节点为最后节点就将pred设置为最后节点
                                                        //由于此时会与申请锁且正在执行
                                                        //addWaiter(Node mode)方法向队尾添加节点的线程
                                                        //存在竞争,所以需要同步

        compareAndSetNext(pred, predNext, null);//由于此时会与申请锁且正在执行addWaiter(Node mode)
                                                //方法pred.next = node;或t.next = node;语句的的线程                                                //存在竞争,所以需要同步并且此处不管是否执行成功,
                                               //都不会影响竞争线程中的pred.next = node;
                                               //这句话的执行;但竞争线程中先执行pred.next = node;
                                               //会造成此处执行失败,也就是说从在竞争时
                                               //执行完pred.next = node;就会使队列处于有效状态

    } else {
        // If successor needs signal, try to set pred's next-link
        // so it will get one. Otherwise wake it up to propagate.
        int ws;
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {//这部分条件对于以上讲的这两种锁方法可能任何情况都不满足,
                                  //只是为了兼容其它锁模式,也可能是这对应的这两种锁模式没有
                                  //想到满足此条件的情况

            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);//此处会与申请锁且调用
                                               //shouldParkAfterFailedAcquire(Node pred, Node node)方法
                                               //进行队列前向遍历的线程存在竞争,因此需要同步,并且
                                               //此处不管是否执行成功,都不会影响竞争线程中的
                                               //pred.next = node;这句话的执行;但竞争线程中
                                               //先执行pred.next = node;会造成此处执行失败

        } else {
            unparkSuccessor(node);//释放他的后续节点
        }

        node.next = node; // help GC
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值