概述

咱们采纳自顶向下的思路来逐渐深刻源码,首先剖析下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