J.U.C同步队列(CLH)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了J.U.C同步队列(CLH)相关的知识,希望对你有一定的参考价值。

参考技术A

在上篇我们聊到AQS的原理,具体参见 《J.U.C|带你走进AQS的内心世界》 。

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

同步队列

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

AQS如何使用它?

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

Node节点面貌?

CLH同步队列的结构图

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

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

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

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

enq

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

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

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

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

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

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

以上是关于J.U.C同步队列(CLH)的主要内容,如果未能解决你的问题,请参考以下文章

J.U.C之AQS:阻塞和唤醒线程

CLH同步队列

Java并发编程实战-----“J.U.C”:CLH队列锁

并发编程-并发容器(J.U.C)核心 AbstractQueuedSynchronizer 抽象队列同步器AQS介绍

[Java并发] AQS抽象队列同步器源码解析--锁获取过程

J.U.C之AQS:同步状态的获取与释放