关于java:AbstractQueuedSynchronizer自顶向下分析三-acquire那些事儿

38次阅读

共计 3310 个字符,预计需要花费 9 分钟才能阅读完成。

概述

咱们采纳自顶向下的思路来逐渐深刻源码,首先剖析下 acquire 这个办法,顾名思义,很多 lock 办法的实现都是基于这个办法,他提供了一个获取许可的形象

acquire

该办法不响应中断,提供获取许可的性能,其中 tryAcquire 是个 protected 办法由子类实现,暂不剖析,整个办法用来实现 lock 办法
如果第一次获取锁失败则调用 acquireQueued 办法入队

 public final void acquire(int arg) {if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            // 为何要自我中断呢?selfInterrupt();}

addWaiter

问题 1 指定独占还是共享模式让以后线程入队,官网正文说是为了性能优化,优化的点在哪呢?貌似还是要判断一次非 null

 private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure 性能优化尝试 enq 办法的疾速执行门路,否则尝试残缺的 enq 办法
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            // 这里都是对共享状态 tail 的操作,须要 CAS 保障
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

enq

节点入队办法,须要保障多线程

tail 节点为空,保障只有一个线程去初始化头节点和尾节点
tail 节点不为空,它的批改步骤如下
1. 批改入参节点的 prev 指针为旧的尾节点。留神此时可能会多线程批改,留神咱们的前提条件是 tail 节点的批改只能被 enq 办法批改,而此时 tail 节点不为 null,所以多线程读到的 tail 节点肯定统一,即使多线程反复写也没有关系
2. 原子更新新的 tail 节点为入参节点。为什么须要原子? 首先 tail 节点是共享状态,必须保障在正确设置了新 tail 节点的前提下设置旧 tail 节点的 next 指针,否则会呈现线程 A 批改了 tail 节点,还未修改旧 tail 节点,线程 B 染指批改了 tail 节点和旧的 tail 节点,导致线程 A 的新 tail 节点和线程 B 的旧 tail 节点更新失落
3. 更新旧的 tail 节点的 next 指针为入参节点,返回旧的 tail 节点


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

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 cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;
        // 把节点绑定的线程赋 null,帮忙 gc 回收
        node.thread = null;

        Node pred = node.prev;
        while (pred.waitStatus > 0)
        //step1 node.prev 指针批改
            node.prev = pred = pred.prev;
        Node predNext = pred.next;
        // 只有这里才设置为已勾销状态
        node.waitStatus = Node.CANCELLED;

        // If we are the tail, remove ourselves.
        if (node == tail && compareAndSetTail(node, pred)) {compareAndSetNext(pred, predNext, null);
        } 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);
            } else {unparkSuccessor(node);
            }

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

node.prev 指针批改剖析

咱们必须晓得的一点是 CLH 队列新增节点永远是 tail 节点。step1 看似没有任何的线程平安,实际上也无需 CAS 来保障。其实这里对共享状态的批改就一处
1.node.prev=pred,这里就是对共享节点的 prev 指针赋值,能够看到其依赖于局部变量 pred,所以它的线程安全性的剖析就变成了 pred 变量的剖析。而 pred=pred.prev,pred.prev 有可能被另一个线程批改,导致 pred 不统一吗?不可能!因为 prev 指针的批改只会在增加节点的时候,而增加节点又只会产生在 tail 节点,所以无论线程执行程序如何,最终都指向了同一个 pred,因而整个语句就是线程平安的。

node 为 tail 节点


1. 原子设置 tail 节点为找到的 pred 节点
为何须要原子?因为 tail 节点不牢靠,tail 节点在有新的线程增加节点的时候就会扭转,如果不必 CAS 保障就有可能笼罩了新增加的节点导致节点失落。
2. 原子设置 pred 节点的 next 指针为 null
这里的原子操作就看不太懂了,如果只是 pred.next=null 仿佛也没问题吧?毕竟第 1 步中曾经保障了原子更新胜利才会进入这里

node 不为 tail 节点且不是 head 节点的后继节点


留神此处的“简单”判断,所有的判断都是为了真正干一件事,把 pred 节点的 next 指针指向 node 节点的后继节点,什么状况下进入该解决逻辑?如下剖析
pred != head &&((ws = pred.waitStatus) == Node.SIGNAL ||(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null
1.pred != head 保障了 node 节点不是 head 的后继节点,
2.pred.thread != null 保障了 pred 节点线程还存在,为什么须要?因为可能有另一个线程勾销了 pred 节点导致 pred 节点的 thread 为 null
1.pred.waitStatus == Node.SIGNAL 为 true, 这意味着 node 的无效前驱节点状态为 signal,它会在必要的时候唤醒后继节点,
保障了 node 的无效前驱节点的状态是 SIGNAL

正文完
 0