Java并发编程——CLH队列

CLH算法实现

写在前面,本文参考[大新博客],在此感谢!(https://www.cnblogs.com/daxin/p/3365324.html)

对于java并发编程,AQS同步器框架的底层实现是通过一个FIFO的队列来维护线程之间的等待与唤醒机制的。此队列不是一个实际存在的队列,而是一个仅仅保存节点之间的关联关系。

CLH队列中的结点QNode中含有一个locked字段,该字段若为true表示该线程需要获取锁,且不释放锁,为false表示线程释放了锁。结点之间是通过隐形的链表相连,之所以叫隐形的链表是因为这些结点之间没有明显的next指针,而是通过myPred所指向的结点的变化情况来影响myNode的行为。CLHLock上还有一个尾指针,始终指向队列的最后一个结点。CLHLock的类图如下所示:

在这里插入图片描述

当一个线程需要获取锁时,会创建一个新的QNode,将其中的locked设置为true表示需要获取锁,然后线程对tail域调用getAndSet方法,使自己成为队列的尾部,同时获取一个指向其前趋的引用myPred,然后该线程就在前趋结点的locked字段上旋转,直到前驱结点释放锁。当一个线程需要释放锁时,将当前结点的locked域设置为false,同时回收前驱结点。如下图所示,线程A需要获取锁,其myNode域为true,些时tail指向线程A的结点,然后线程B也加入到线程A后面,tail指向线程B的结点。然后线程A和B都在它的myPred域上旋转,一量它的myPred结点的locked字段变为false,它就可以获取锁了。明显线程A的myPred locked域为false,此时线程A获取到了锁。

CLH LOCK示意图
整个CLH的代码如下,其中用到了ThreadLocal类,将QNode绑定到每一个线程上,同时用到了AtomicReference,对尾指针的修改正是调用它的getAndSet()操作来实现的,它能够保证以原子方式更新对象引用。

public class CLHLock {

    AtomicReference<QNode> tail = new AtomicReference<QNode>(new QNode());
    ThreadLocal<QNode> myPred;
    ThreadLocal<QNode> myNode;

    public static class QNode {
        //注意这个地方 如果不加volatile则会导致线程永远死循环
        //关于volatile的用法在我的另外一篇文章 http://www.cnblogs.com/daxin/p/3364014.html
        public volatile boolean locked = false;
    }

    public CLHLock() {
        myNode = new ThreadLocal<QNode>() {
            protected QNode initialValue() {
                return new QNode();
            }
        };
        myPred = new ThreadLocal<QNode>() {
            protected QNode initialValue() {
                return null;
            }
        };
    }

    public void lock() {
        QNode qnode = myNode.get();
        qnode.locked = true;
        QNode pred = tail.getAndSet(qnode);
        myPred.set(pred);
        while (pred.locked) {
            //非阻塞算法
        }
    }

    public void unlock() {
        QNode qnode = myNode.get();
        qnode.locked = false;
        myNode.set(myPred.get());
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
CLH队列和条件队列是两个不同的概念。 CLH队列是AQS(AbstractQueuedSynchronizer)内部维护的一种FIFO队列,用于实现同步机制。它采用双向链表保存,使用prev和next相互链接,每个节点表示一个线程,按照线程的先后顺序排队等待获取锁资源。CLH队列的特点是线程之间通过不断自旋来检查前一个线程是否已经释放锁资源,从而实现高效的等待队列。 条件队列则是AQS中的一种数据结构,用于实现线程的等待与唤醒机制。条件队列是使用单向列表保存的,使用nextWaiter来连接节点。每个节点表示一个等待条件的线程,当线程调用await()方法时,它将被移动到条件队列中等待。当满足某个条件时,其他线程可以调用signal()或signalAll()方法来唤醒条件队列中的线程。 总结起来,CLH队列用于实现同步机制中的线程排队等待获取锁资源,而条件队列用于实现线程的等待与唤醒机制。它们分别采用不同的数据结构和链接方式。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [并发之AQS原理(二) CLH队列与Node解析](https://blog.csdn.net/weixin_30561177/article/details/99097474)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [浅谈Java并发 J.U.C之AQS:CLH同步队列](https://download.csdn.net/download/weixin_38694336/12749163)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值