java clh队列_J.U.C|同步队列(CLH)

一、写在前面

在上篇我们聊到AQS的原理,具体参见《J.U.C|AQS原理》。

这篇我们来给大家聊聊AQS中核心同步队列(CLH)。

二、什么是同步队列(CLH)

同步队列

一个FIFO双向队列,队列中每个节点等待前驱节点释放共享状态(锁)被唤醒就可以了。

AQS如何使用它?

AQS依赖它来完成同步状态的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。

Node节点面貌?

static final class Node {

// 节点分为两种模式: 共享式和独占式

/** 共享式 */

static final Node SHARED = new Node();

/** 独占式 */

static final Node EXCLUSIVE = null;

/** 等待线程超时或者被中断、需要从同步队列中取消等待(也就是放弃资源的竞争),此状态不会在改变 */

static final int CANCELLED = 1;

/** 后继节点会处于等待状态,当前节点线程如果释放同步状态或者被取消则会通知后继节点线程,使后继节点线程的得以运行 */

static final int SIGNAL = -1;

/** 节点在等待队列中,线程在等待在Condition 上,其他线程对Condition调用singnal()方法后,该节点加入到同步队列中。 */

static final int CONDITION = -2;

/**

* 表示下一次共享式获取同步状态的时会被无条件的传播下去。

*/

static final int PROPAGATE = -3;

/**等待状态*/

volatile int waitStatus;

/**前驱节点 */

volatile Node prev;

/**后继节点*/

volatile Node next;

/**获取同步状态的线程 */

volatile Thread thread;

/**链接下一个等待状态 */

Node nextWaiter;

// 下面一些方法就不贴了

}

CLH同步队列的结构图

5a7b55432b89f8e913f16f430feafde9.png

这里是基于CAS(保证线程的安全)来设置尾节点的。

三、入列操作

如上图了解了同步队列的结构, 我们在分析其入列操作在简单不过。无非就是将tail(使用CAS保证原子操作)指向新节点,新节点的prev指向队列中最后一节点(旧的tail节点),原队列中最后一节点的next节点指向新节点以此来建立联系,来张图帮助大家理解。

0a054ce93e595608b8cf240e7953fc83.png

源码

源码我们可以通过AQS中的以下两个方法来了解下

addWaiter方法

private Node addWaiter(Node mode) {

// 以给定的模式来构建节点, mode有两种模式

// 共享式SHARED, 独占式EXCLUSIVE;

Node node = new Node(Thread.currentThread(), mode);

// 尝试快速将该节点加入到队列的尾部

Node pred = tail;

if (pred != null) {

node.prev = pred;

if (compareAndSetTail(pred, node)) {

pred.next = node;

return node;

}

}

// 如果快速加入失败,则通过 anq方式入列

enq(node);

return node;

}

先通过addWaiter(Node node)方法尝试快速将该节点设置尾成尾节点,设置失败走enq(final Node node)方法

enq

private Node enq(final Node node) {

// CAS自旋,直到加入队尾成功

for (;;) {

Node t = tail;

if (t == null) { // 如果队列为空,则必须先初始化CLH队列,新建一个空节点标识作为Hader节点,并将tail 指向它

if (compareAndSetHead(new Node()))

tail = head;

} else {// 正常流程,加入队列尾部

node.prev = t;

if (compareAndSetTail(t, node)) {

t.next = node;

return t;

}

}

}

}

通过“自旋”也就是死循环的方式来保证该节点能顺利的加入到队列尾部,只有加入成功才会退出循环,否则会一直循序直到成功。

上述两个方法都是通过compareAndSetHead(new Node())方法来设置尾节点,以保证节点的添加的原子性(保证节点的添加的线程安全。)

四、出列操作

同步队列(CLH)遵循FIFO,首节点是获取同步状态的节点,首节点的线程释放同步状态后,将会唤醒它的后继节点(next),而后继节点将会在获取同步状态成功时将自己设置为首节点,这个过程非常简单。如下图

33a0a7a20a518741268d3bac0f510581.png

设置首节点是通过获取同步状态成功的线程来完成的(获取同步状态是通过CAS来完成),只能有一个线程能够获取到同步状态,因此设置头节点的操作并不需要CAS来保证,只需要将首节点设置为其原首节点的后继节点并断开原首节点的next(等待GC回收)应用即可。

五、总结

聊完后我们来总一下,同步队列就是一个FIFO双向对队列,其每个节点包含获取同步状态失败的线程应用、等待状态、前驱节点、后继节点、节点的属性类型以及名称描述。

其入列操作也就是利用CAS(保证线程安全)来设置尾节点,出列就很简单了直接将head指向新头节点并断开老头节点联系就可以了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值