zoukankan      html  css  js  c++  java
  • ReentrantLock 非公平锁不公平在哪

    重入锁关键地带:

    1:使用unsafe的cas方式对AQS中的state成员变量进行“原子加一”操作。

    2:如果当前线程多次lock,相当于对state在原有值基础上继续加一操作;释放锁的条件为“原子减一”到0为止。

    3:ReentrantLock在非公平锁问题:

    严格上讲并不是完全的非公平,当线程未获取到锁,进入线程Node链表时,并且链表有多个节点的情况下仍然要排队park,等待链表的先驱节点去unPark后才能继续执行。

    而非公平是在首次尝试加锁的时候没有去理会线程的等待链表,如果首次尝试失败以后,会尝试判断锁是否被释放,如果当前锁已经被释放,二次尝试加锁的时候仍然不理会其它线程的等待链表,会直接尝试枷锁。

    4:公平锁的关注点在hasQueuedPredecessors方法中:
    (1)如果链表未创建,尝试直接对资源加锁;
    (2)如果链表的头节点的next节点为null,也直接尝试加锁(实际就是当前线程重入);
    (3)如果当前线程和持有锁的线程是同一线程,也直接加锁(实际就是当前线程重入);

     public final boolean hasQueuedPredecessors() {
            // The correctness of this depends on head being initialized
            // before tail and on head.next being accurate if the current
            // thread is first in queue.
            Node t = tail; // Read fields in reverse initialization order
            Node h = head;
            Node s;
            return h != t &&
                ((s = h.next) == null || s.thread != Thread.currentThread());
        }

    ps:链表设计的比较巧妙,头节点要么是持有锁的节点,要么是Tread数据域是null的节点。因为在当前资源首次加锁的情况下是不进入链表等待的,作者虚拟出一个“替代节点”,一旦首次加锁执行完毕,那么会移除这个“替代节点”,让真正持有锁的节点成为链表的头节点。

     public final boolean hasQueuedPredecessors() {
            // The correctness of this depends on head being initialized
            // before tail and on head.next being accurate if the current
            // thread is first in queue.
            Node t = tail; // Read fields in reverse initialization order
            Node h = head;
            Node s;
            return h != t &&
            //下面这句话,实际就是当前线程重入
                ((s = h.next) == null || s.thread != Thread.currentThread());
        }

    5:入链表以后等待加锁的过程:

    当一个节点入队以后,方法会返回该节点对象;
    (1)如果发现当前节点的前一个节点是头节点,则当前线程立马尝试一次加锁,不用等待头节点主动unPark,当然头节点也会进行unPark;如果这次加锁失败则判断前一个节点是否Signal状态,如果是该状态则当前线程会自己park中断掉,等待先驱节点的唤醒。如果先驱节点不是Signal而是CANCELLED,一直沿着链表向前找,直到找到Signal后将自己链接到该节点的后面。

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

    (2)如果前驱结点不是head节点,那么直接执行是否中断逻辑。

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

    汇总:
    可以看出ReentrantLock的加锁机制相比1.6以前的sync关键字是轻量的:
    (1)当只有1个线程加锁时,没有其它线程资源占用的情况,它并不会初始化等待链表,只有非常轻量的CAS操作。

    (2)而且ReentrantLock支持对同一个资源多次加锁。

    (3)即使存在队列,那么队列中第一个等待线程节点对象,会主动尝试去加锁。

  • 相关阅读:
    修改服务器时间以后 cookie无法保存
    SqlServer 2017 下载地址及密钥
    ASP.NET CORE 开发路线
    在ASP.NET CORE下生成PDF文档
    Redis 学习笔记2
    Redis 学习笔记1
    notepad++文件对比
    Notepad++主题设置与推荐
    Vivado HLx 2019.1下载、安装与激活
    Tensorflow机器学习入门——cifar10数据集的读取、展示与保存
  • 原文地址:https://www.cnblogs.com/zzq-include/p/11995473.html
Copyright © 2011-2022 走看看