首页天道酬勤,

,

张世龙 05-04 18:05 55次浏览

目录

一.个人资料

节点类

3.clh队列

另一方面,个人资料AQS是JUC的核心,无论是信号、CDL还是再确认,背后都有AQS的影子。 这些类的同步过程一般如下:

您可以很好地理解tryAcquire和tryRelease流程。 从CAS上修改AQS的状态值。 重要的是,doAcquire和doRelease如何管理多个线程的状态,以及如何确定哪些线程可以获得锁定。 答案是,AQS在其内部管理链表,所有线程都添加到此链表中进行管理。 此链表称为CLH队列,因为它使用(改进的) CLH锁,并且在公平实现中具有先进先出特性。

Node类首先检查doAcquireInterruptibly方法的代码。

专用语音服务(intarg ) throwsinterruptedexception ) finalnodenode=addwaiter ) node.exclusive );可以看到,在方法开始时,首先调用了addWaiter方法。 将当前线程添加到等待队列的过程中,可以很容易地了解链表。

私有节点向导(节点模式)节点节点=新节点(thread.currentthread ),模式); 节点预览=tail; 预打!=null}{node.prev=pred; if (比较安装(pred,node ) ) { pred.next=node; 返回节点; } Enq (节点); 返回节点; 可以看到,AQS将线程包装在Node中,然后入队。 Node是AQS的内部类,定义并不复杂。

staticfinalclassnode { staticfinalnodeshared=new node (; 静态金融节点Exclusive=null; 静态最终输入取消=1; 静态final int signal=-1; 静态最终输入条件=-2; 静态金融int propagate=-3; volatile int waitStatus; 电压节点预置; 电压节点下一步; 电压读数读数; 节点下一个服务员; finalbooleanisshared ((returnnextwaiter==shared; } final Node predecessor () throwsnullpointerexception ) nodep=prev; if(p==null ) throw new NullPointerException ); else return p; } Node () } Node ) threadthread,Node mode ) ) { this.nextWaiter=mode; this.thread=thread; }node(threadthread,int waitStatus ) { this.waitStatus=waitStatus; this.thread=thread; }最核心的成员变量是thread、wait

Status、nextWaiter,thread保存了节点对应的线程;waitStatus用于标记节点状态;nextWaiter则决定了当前获取/释放锁的模式,有SHARED、QUEUED、EXCLUSIVE三种,例如信号量就工作在共享模式下(其acquire方法实际调用了acquireSharedInterruptibly方法)。

Node节点的状态有如下五种:

状态解释CANCELLED当前节点已经取消等待(超时或中断),该状态下的节点不会进入其他状态,也不会再阻塞。SIGNAL后继结点已经或即将阻塞,当前节点需要唤醒后继结点。后继结点获取锁的时候,必须先收到SIGNAL,才能调用tryAcquire(如果再次失败就会重新阻塞)CONDITION该节点正处于Condition队列中(ReentrantLock的Condition),因为等待condition.signalAll()而阻塞PROPAGATE仅用于共享模式下、释放锁时的队列头节点,用于向整个队列传播锁释放信号0以上四种情形之外的状态,一般是新建的节点三.CLH队列

在AQS类文件的开头,作者添加了很长一段注释,向开发者解释CLH队列,以及AQS对CLH队列的使用。

AQS里面的CLH队列是CLH同步锁的一种变形。其主要从两方面进行了改造:节点的结构与节点等待机制。

在结构上,AQS类引入了头结点和尾节点,他们分别指向队列的头和尾,尝试获取锁、入队列、释放锁等实现都与头尾节点相关:

private transient volatile Node head; private transient volatile Node tail;

从addWaiter可以看到,CLH队列的入队实际上就是把当前节点设置为队尾,那么相应的,出队就是处理队首节点。

在Node类内,还有对前驱节点、后继节点的引用。前驱结点主要用在取消等待的场景:当前节点出队后,需要把后继结点连接到前驱结点后面;后继结点的作用是避免竞争,在doRelease方法中,当前节点出队后,会让自己的后继结点接替自己获取锁,有了明确的继承关系,就不会出现竞争了。

在等待机制上由原来的自旋改成阻塞+唤醒,以doAcquireInterruptibly为例,parkAndCheckInterrupt方法使用LockSupport令当前线程阻塞,直到收到信号被唤醒后,进入下一轮自旋:

private void doAcquireInterruptibly(int arg) throws InterruptedException { ... if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } ... } private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }

可见CLH队列的优点如下:

相比于纯自旋,自旋+阻塞+唤醒的机制能够很好地兼顾性能与效率同步操作只涉及头节点、尾节点、当前节点、前驱结点、后继结点,对整个队列影响很小,明显优于整体加锁或者分段加锁通过后继结点机制,明确了获得锁的顺序(除非出现信号量申请许可过多无法获取锁这种情况,此时只需要传播状态,继续向下寻找合适节点继承锁即可),避免了竞争
,