java 可重入的公平锁

ReenLock可重入的公平锁

img

img


在执行这行代码的时候,会调用hasQueuedPredecessors这个方法,这个方法有点不懂
他们说,这个方法是判断是否有前一个节点,说这个方法也是实现公平锁的原因,
但我是一点也看不懂,这个方法里面为什么要这样进行判断,有人说这里又无法实现公平,因为它可能会被插队

hasQueuedPredecessors 这个判断当前线程是否位于等待队列的头结点,止于为什么判断这么复杂咱们先不谈。你没看懂的原因是有几点,第一好多逻辑运算符,第二 好多有取反的逻辑。咱们先做个假设~

首先呢

        Node t = tail; //尾结点
        Node h = head;//头结点
        Node s;
        // 判断头是否等于尾结点(取反,有些网上解释不太严谨,后面再说。),然后判断首节点的next节点与当前线程是否一个(取反)
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());

好了 我们来解释为什么网上说的好多是不严谨的。因为AQS 中的等待队列是使用双向链表来表示的。所以说每个节点都有prev和next。当然head节点是“null”为什么呢?因为只有在存在线程持有锁的时候,后续线程加入锁竞争时候才会存在这个双向链表,严谨的标识当前head节点标识正在持有锁的节点。好了那么为什么说是为了判断等待队列中是否有元素呢,第一次尝试获取锁,比如线程a,head和tail都为null(可以理解为没有锁竞争),然后顺利的获取到锁;然后当前琐未释放,另外一个线程b进行获取锁,当前锁已被占用,开始走加入等待队列逻辑,逻辑中可以看到初始化队列的过程;此时有线程a持有锁,所以head的next节点指向了线程b,尾结点tail的prev指向线程a(其中只有next节点指向线程b其他信息均为null);假如现在线程a释放了锁,唤醒线程b开始获取锁,此时设定当前线程为head,代码如下:

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);
        }
    }
  //将线程置为空,以及前驱节点,也就是说当前线程获取到锁,然后将等待队列头指向当前节点,并将描述当前线程的信息值为空。
  private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

加入多个线程加入到等待队列,只有当前线程的node.prev与当前的head相等,每次加入队列尾部,标识当前线程节点的pre为上一个线程的head。

hasQueuedPredecessors这个方法也不是实现公平锁的关键,等待队列才是关键,该方法只是多了几个判断,多了一层保障,首先判断等待队列中是否有元素~ 如果有元素的话判断head的next线程是否是当前线程。

其中为什么说他也不是完全公平的,首先:使用了大量的cas;其次:可重入锁,获取锁的线程可以多次获取锁,也不可能是先来后到。
以上拙见,还望能得到帮助,如果能的话方便点一个采纳~ 谢谢。

不知道你这个问题是否已经解决, 如果还没有解决的话:
  • 这篇博客: 知识积累-ReentrantLock公平锁的实现中的 hasQueuedPredecessors() 部分也许能够解决你的问题, 你可以仔细阅读以下内容或者直接跳转源博客中阅读:
    static final class FairSync extends Sync {
    
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
    		...
        }
    }
    
    • 公平锁和非公平锁,主要是在方法 tryAcquire中,是否有hasQueuedPredecessors()判断。

如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^