CLH锁的简单实现

CLH锁(Craig, Landin, and Hagersten Locks (CLH Locks)) 是一种基于链表的自旋锁,采用类似队列的方式管理线程的排队。该锁的核心思想是通过在每个线程节点中存储锁的状态来确保线程的顺序访问,而不直接依赖于共享的状态。CLH 锁有效减少了线程竞争时的缓存一致性问题。

下面是一个简单的 Java 实现,模拟了 CLH 锁的工作原理。


CLH 锁的核心思想

  • 每个线程都有一个自己的节点 (CLHNode),这个节点包含一个 locked 标志位,表示该线程是否正在持有锁。
  • tail 是指向队列末尾的指针,AtomicReference 用于确保操作的原子性。
  • 当线程试图获取锁时,会自旋等待直到其前驱节点释放锁。
  • 线程解锁时,仅将自己的 locked 标志设置为 false,并不通知后继线程,后继线程通过检查前驱节点的状态来判断是否可以获得锁。

CLH 锁的Java实现

import java.util.concurrent.atomic.AtomicReference;

public class CLHLock {

    // 每个线程有一个自己的节点
    private ThreadLocal<CLHNode> myNode;
    // 当前队列的尾部
    private AtomicReference<CLHNode> tail;

    // CLH锁节点类
    static class CLHNode {
        volatile boolean locked = false;  // 线程是否持有锁,默认false,表示不持有锁
    }

    public CLHLock() {
        tail = new AtomicReference<>(null);  // 初始时队列为空
        myNode = ThreadLocal.withInitial(CLHNode::new);  // 每个线程都有自己的节点
    }

    // 上锁方法
    public void lock() {
        CLHNode node = myNode.get();  // 获取当前线程的节点
        node.locked = true;  // 设置节点为锁定状态

        // 将当前节点设置为队列尾部
        CLHNode predecessor = tail.getAndSet(node);  // 获取当前队尾节点,并将当前节点设置为新尾节点

        // 如果前驱节点仍在锁定状态,当前线程进入自旋等待
        if (predecessor != null) {
            while (predecessor.locked) {
                // 自旋等待前驱线程释放锁
            }
        }
    }

    // 解锁方法
    public void unlock() {
        CLHNode node = myNode.get();  // 获取当前线程的节点
        node.locked = false;  // 设置节点为解锁状态

        // 不需要通知后继线程,后继线程通过检查前驱节点的状态来判断是否可以获得锁
    }

    public static void main(String[] args) throws InterruptedException {
        final CLHLock lock = new CLHLock();

        // 多线程测试
        Runnable task = () -> {
            lock.lock();
            try {
                // 临界区代码
                System.out.println(Thread.currentThread().getName() + " is in critical section.");
            } finally {
                lock.unlock();
            }
        };

        // 创建多个线程并执行
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(task);
            threads[i].start();
        }

        // 等待所有线程执行完毕
        for (int i = 0; i < 5; i++) {
            threads[i].join();
        }
    }
}

代码解析:

  1. CLHNode 类

    • locked 字段表示该线程是否持有锁。默认情况下,locked 是 false,表示该线程未获得锁。
    • 每个线程有自己的 CLHNode 节点,节点存储了当前线程的锁状态。
  2. CLHLock 类

    • 使用 AtomicReference<CLHNode> 作为队列的尾部 tail,确保对尾部的操作是原子性的。
    • ThreadLocal<CLHNode> 为每个线程分配一个独立的节点,避免多个线程共享同一个节点。
  3. lock() 方法

    • 当前线程获取锁时,首先将其自己的节点的 locked 标志设置为 true,然后尝试将该节点添加到队列的尾部。
    • 通过 tail.getAndSet(node) 原子性地将当前节点设置为队列的尾部,并返回之前的尾部节点(即前驱节点)。
    • 如果前驱节点的 locked 标志仍然是 true,当前线程会自旋等待直到前驱节点释放锁。
  4. unlock() 方法

    • 当前线程释放锁时,简单地将 locked 标志设置为 false。由于 CLH 锁的结构,后继线程会通过检查前驱节点的状态来决定是否可以进入临界区,因此无需显式地通知后继线程。
  5. main 方法

    • 创建了 5 个线程,并让它们并发地执行相同的任务。每个线程在临界区执行时会首先调用 lock() 获取锁,完成后调用 unlock() 释放锁。

输出实例


参考

CLH lock queue的原理解释及Java实现 - 简书

深入理解AQS的CLH队列_clh 队列-优快云博客

https://juejin.cn/post/7329457173912698895

并发编程之CLH同步队列 出队入队详解 - 《船长》 - 博客园

AQS的前菜—详解CLH队列锁_clh锁-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值