自旋锁和 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 开销。
- 功能单一,不改造不能支持复杂的功能。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)