高并发场景下优化spinlock

数据库并发依靠闩锁(Latch、Spinlock、Rwlock、LWLock等最终的机制原理都十分相近,我们就取Oracle的Latch来做此类锁的统称)和队列锁机制来实现必要的串行化。串行化是为了一致性访问某个资源所必须承受的并发环境下的代价。队列锁最为典型的就是行锁表锁等应用级的锁,不在我们今天讨论的范围内,今天我们重点讨论的是闩锁。闩锁的目的是为了一致性访问数据库内存中的共享内存结构,避免内存结构因为不一致的读写而导致混乱。不管是核心态的数据还是用户态的共享内存,都需要通过串行化控制来保证其一致性的读写。为了确保这种一致性,这些串行化的控制都需要至少一次全局串行化来达到最终的目的。这个全局串行化,一般来说就是使用的是和CPU直接相关的原子操作,自旋锁SPINLOCK。

前阵子我发过一个关于闩锁与SPINLOCK的帖子,其中简单的提到了CPU的PAUSE指令对SPINLOCK的影响。当自旋锁无法获得SPINLOCK的时候,已经占用了CPU的线程没必要放弃CPU进入休眠,等待再次尝试获得SPINLOCK,因此会在该CPU上自旋。不过对于争用比较严重的系统,自旋多次后,有可能仍然无法获得SPINLOCK。因为自旋时该线程需要持有CPU的串行锁,因此,对于具有多个线程(比如至强芯片是双线程的)的CPU来说,这种长时间自旋而无法获得SPINLOCK的情况会导致另外一个CPU线程也被锁死,无法执行。为了解决这个问题,LINUX针对SPINLOCK的算法做了一些优化。

上面是通过perf工具跟踪的结果,我们可以看到_raw_qspin_lock首先通过lock cmpxchg来获得spinlock,如果当前SPINLOCK无人持有,则直接获得SPINLOCK并返回,否则就调用queued_spin_lock_slowpath来继续自旋。今天篇幅有限,我们不讨论详细的细节。直接看queued_spin_lock_slowpath的汇编代码。

优化 AQS 的 CAS 操作以减少高并发场景下自旋开销的方法如下: #### 限制自旋次数 在 JUC 中有些地方限制了 CAS 自旋的次数,例如 BlockingQueue 的 SynchronousQueue。通过设置最大自旋次数,当达到最大次数仍未成功时,线程放弃自旋,避免长时间自旋带来的 CPU 开销。示例代码如下: ```java import java.util.concurrent.atomic.AtomicInteger; public class LimitedSpinCAS { private AtomicInteger state = new AtomicInteger(0); private static final int MAX_SPIN_TIMES = 100; public boolean tryAcquire() { int spinCount = 0; while (spinCount < MAX_SPIN_TIMES) { if (state.compareAndSet(0, 1)) { return true; } spinCount++; } return false; } public void release() { state.set(0); } } ``` #### 采用锁升级策略 在锁竞争不激烈、锁持有时间短的场景下,使用自旋锁代替阻塞锁,避免线程上下文切换的开销;当竞争激烈时,升级为阻塞锁,避免大量线程长时间自旋消耗 CPU 资源。 ```java import java.util.concurrent.atomic.AtomicBoolean; public class LockUpgrade { private AtomicBoolean spinLock = new AtomicBoolean(false); private Object monitor = new Object(); private boolean isBlocked = false; public void lock() { if (!isBlocked) { while (!spinLock.compareAndSet(false, true)) { // 自旋 } if (isContended()) { // 竞争激烈,升级为阻塞锁 isBlocked = true; spinLock.set(false); synchronized (monitor) { while (!spinLock.compareAndSet(false, true)) { try { monitor.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } } else { synchronized (monitor) { while (!spinLock.compareAndSet(false, true)) { try { monitor.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } } public void unlock() { spinLock.set(false); if (isBlocked) { synchronized (monitor) { monitor.notify(); } } } private boolean isContended() { // 判断是否竞争激烈的逻辑 return false; } } ``` #### 减小锁粒度 使用分段锁,将一个大锁拆分成多个小锁,减少锁的竞争。例如 ConcurrentHashMap 中的实现,不同的段可以被不同的线程同时访问,从而提高并发性能。 #### 结合不同类型的锁 在不同的场景下选择合适的锁类型。例如,在读多写少的情况下,使用 ReentrantReadWriteLock 比 ReentrantLock 更加适用,因为它允许多个线程同时进行读操作,提高并发度[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值