从JDK源码角度看并发锁的优化

在CLH锁核心思想的影响下,JDK并发包以CLH锁做为基础而设计,其中主要是考虑到CLH锁更容易实现取消与超时功能。比起原来的CLH锁已经作了很大的改造,主要从两方面进行了改造:节点的结构与节点等待机制。javascript

在结构上引入了头结点和尾节点,他们分别指向队列的头和尾,尝试获取锁、入队列、释放锁等实现都与头尾节点相关,而且每一个节点都引入前驱节点和后后续节点的引用;在等待机制上由原来的自旋改为阻塞唤醒。如图,经过前驱后续节点的引用一节节链接起来造成一个链表队列,对于头尾节点的更新必须是原子的。下面详细看看入队、检测挂起、释放出队、超时、取消等操做。html

入队

整块逻辑实际上是用一个无限循环进行CAS操做,即用自旋方式竞争直到成功。将尾节点tail的旧值赋予新节点node的前驱节点,并尝试CAS操做将新节点node赋予尾节点tail,原先的尾节点的后续节点指向新建节点node。完成上面步骤就创建起一条如图所示的链表队列。代码简化以下:java

for (;;) {  
    Node t = tail;  
    node.prev = t;  
    if (compareAndSetTail(t, node)) {  
        t.next = node;  
        return node;  
    }  
}复制代码

检测挂起

上面咱们说到节点等待机制已经被JDK并发做者由自旋机制改形成阻塞机制,一个新建的节点完成入队操做后,若是是自旋则直接进入循环检测前驱节点是否为头结点便可,但如今被改成阻塞机制,当前线程将首先检测是否为头结点且尝试获取锁,若是当前节点为头结点并成功获取锁则直接返回,当前线程不进入阻塞,不然将当前线程阻塞。代码简化以下:node

for (;;) {  
    if (node.prev == head)  
        if(尝试获取锁成功){  
             head=node;  
             node.next=null;  
             return;  
         }  
   阻塞线程  
}复制代码

释放出队

出队的主要工做是负责唤醒等待队列中后续节点,让全部等待节点环环相接,每条线程有序地往下执行。代码简化以下:并发

Node s = node.next;
唤醒节点s包含的线程复制代码

超时

在支持超时的模式下须要LockSupport类的parkNanos方法支持,线程在阻塞一段时间后会自动唤醒,每次循环将累加消耗时间,当总消耗时间大于等于自定义的超时时间时就直接分返。代码简化以下:this

for (;;) {  
    尝试获取锁  
    if (nanosTimeout <= 总消耗时间)  
        return;  
    LockSupport.parkNanos(this, nanosTimeout);  
 }复制代码

取消

队列中等待锁的队列可能由于中断或超时而涉及到取消操做,这种状况下被取消的节点再也不进行锁竞争。此过程主要完成的工做是将取消的节点移除,先将节点的。先将节点node状态设置成取消,再将前驱节点pred的后续节点指向node的后续节点,这里因为涉及到竞争,必须经过CAS进行操做,CAS操做就算失败也没必要理会,由于已经改了节点的状态,在尝试获取锁操做中会循环对节点的状态判断。spa

node.waitStatus = Node.CANCELLED;  
Node pred = node.prev;  
Node predNext = pred.next;  
Node next = node.next;  
compareAndSetNext(pred, predNext, next);复制代码

====广告时间,可直接跳过====线程

鄙人的新书《Tomcat内核设计剖析》已经在京东预售了,有须要的朋友能够到 item.jd.com/12185360.ht… 进行预约。感谢各位朋友。设计

=========================code

欢迎关注:

相关文章
相关标签/搜索