自旋锁和 CLH 锁的简单实现

自旋锁

自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

自旋锁是互斥锁的一种实现,Java 实现如下所示。

public class SpinLock {
    private AtomicReference<Thread> owner = new AtomicReference<Thread>();

    public void lock() {
        Thread currentThread = Thread.currentThread();
        // 如果锁未被占用,则设置当前线程为锁的拥有者
        while (!owner.compareAndSet(null, currentThread)) {
        }
    }

    public void unlock() {
        Thread currentThread = Thread.currentThread();
        // 只有锁的拥有者才能释放锁
        owner.compareAndSet(currentThread, null);
    }
}

测试代码如下。

public class SpinLockTest {

    public static class Accumulator implements Runnable {
        SpinLock lock = new SpinLock();
        int i;

        @Override
        public void run() {
            for (int j = 0; j < 10000; j++) {
                lock.lock(); // 加锁
                i++;
                lock.unlock(); // 解锁
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Accumulator accumulator = new Accumulator();
        Thread t1 = new Thread(accumulator);
        Thread t2 = new Thread(accumulator);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(accumulator.i); // 预期值 20000
    }
}

输出如下。

20000

Process finished with exit code 0

优缺点
优点:

  • 自旋锁实现简单,同时避免了操作系统进程调度和线程上下文切换的开销。

缺点:

  • 锁饥饿问题。在锁竞争激烈的情况下,可能存在一个线程一直被其他线程“插队”而一直获取不到锁的情况。
  • 性能问题。在实际的多处理上运行的自旋锁在锁竞争激烈时性能较差。

CLH 锁

CLH 锁是对自旋锁的一种改进,有效的解决了以上的两个缺点。首先它将线程组织成一个队列,保证先请求的线程先获得锁,避免了饥饿问题。其次锁状态去中心化,让每个线程在不同的状态变量中自旋,这样当一个线程释放它的锁时,只能使其后续线程的高速缓存失效,缩小了影响范围,从而减少了 CPU 的开销。

Java实现。

public class CLH {
    private final ThreadLocal<Node> node = ThreadLocal.withInitial(Node::new);
    private final AtomicReference<Node> tail = new AtomicReference<>(new Node());

    private static class Node {
        private volatile boolean locked; // volatile 防止指令重排
    }

    public void lock() {
        Node node = this.node.get();
        node.locked = true;
        Node pre = this.tail.getAndSet(node);
        while (pre.locked) ;
    }

    public void unlock() {
        Node node = this.node.get();
        node.locked = false;
        this.node.set(new Node()); // 防止死锁
    }
}

测试代码如下。

public class CLHTest {
    public static class Accumulator implements Runnable {
        CLH lock = new CLH();
        int i;

        @Override
        public void run() {
            for (int j = 0; j < 10000; j++) {
                lock.lock();
                i++;
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Accumulator accumulator = new Accumulator();
        Thread t1 = new Thread(accumulator);
        Thread t2 = new Thread(accumulator);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(accumulator.i); // 预期值 20000
    }
}

输出如下。

20000

Process finished with exit code 0

优缺点:
优点:

  • 性能优异,获取和释放锁开销小。CLH 的锁状态不再是单一的原子变量,而是分散在每个节点的状态中,降低了自旋锁在竞争激烈时频繁同步的开销。在释放锁的开销也因为不需要使用 CAS 指令而降低了。
  • 公平锁。先入队的线程会先得到锁。
  • 实现简单,易于理解。
  • 扩展性强。

缺点:

  • 因为有自旋操作,当锁持有时间长时会带来较大的 CPU 开销。
  • 功能单一,不改造不能支持复杂的功能。

参考自
Java AQS 核心数据结构-CLH 锁

posted @   DaleLee  阅读(67)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示