之前一直有看到自旋这个概念,但没有研究过,今天大概讲一下什么是自旋。
我下面是一个while永真的循环:
public class SpinDemo {
public static void main(String[] args) {
while (true){
System.out.println("hello world");
}
}
}
这就是自旋,当然这是一个没有意义的形式上的自旋。
不过这种方式在日常编码中不要随便乱用,对CPU的占用会非常高,我本地试了一下基本两秒钟就把CPU占到100%了。
如果真的要使用,也尽量加一个休眠时间或次数限制:
public class WhileDemo {
public static void main(String[] args) {
while (true){
System.out.println("hello world");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
此时CPU的使用率直接从100%降到了30%。
下面讲一些Java中良好的自旋实现方式:
在 Java 中,自旋(Spin) 的核心是「线程不放弃 CPU 执行权,通过循环持续检查条件」,本质是 “用 CPU 短时间忙等换取阻塞 / 唤醒的高开销”。它并非直接暴露给开发者的 API,而是隐藏在 JDK 底层、并发框架、锁机制中,作为性能优化手段存在。
以下是 Java 中自旋的主要体现场景,按 “底层机制→并发工具→框架应用” 的逻辑梳理:
一、JDK 底层核心机制:自旋是基础优化
这是 Java 自旋最核心的应用场景,支撑着并发编程的底层性能,开发者无需手动干预,但理解其原理能更好地排查问题。
1. CAS 操作与原子类(自旋的最底层体现)
Java 中 java.util.concurrent.atomic 包下的原子类(如 AtomicInteger、AtomicReference),底层依赖 **CAS(Compare-And-Swap)**操作,而 CAS 本身就是通过 “自旋循环” 实现的:
CAS 核心逻辑:尝试修改目标值,若修改失败(被其他线程抢占),则重复尝试(自旋),直到修改成功或达到阈值。
示例(AtomicInteger.incrementAndGet() 底层逻辑简化):
public final int incrementAndGet() {
while (true) {
int current = get(); // 获取当前值
int next = current + 1; // 计算目标值
// CAS 尝试修改,成功则返回,失败则自旋重试
if (compareAndSet(current, next)) {
return next;
}
}
}
特点:自旋次数极少(通常几次内成功),无锁竞争时效率极高,是并发编程的 “基础原子操作”。
2. 自旋锁(JDK 锁机制的优化)
Java 中的锁(如 ReentrantLock、synchronized 升级后的锁),为了避免 “短等待” 场景下的线程阻塞,会引入自旋优化:
核心逻辑:线程获取锁失败时,不立即阻塞,而是自旋几次(默认 10 次左右),持续检查锁是否被释放;若自旋后仍未获取到锁,再进入阻塞状态。
具体体现:
ReentrantLock:底层依赖 AQS(AbstractQueuedSynchronizer),AQS 的 acquireQueued() 方法中会通过自旋尝试获取锁;
synchronized 锁(JDK 1.6+):锁升级过程中,从 “偏向锁” 升级为 “轻量级锁” 时,会通过自旋 CAS 尝试释放锁(避免直接升级为重量级锁)。
示例(AQS 自旋逻辑简化):
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) { // 自旋循环
final Node p = node.predecessor();
// 尝试获取锁,成功则退出自旋
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 自旋失败后,判断是否需要阻塞
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
interrupted = true;
}
}
} finally {
if (failed) cancelAcquire(node);
}
}
特点:自旋是 “可控的”,有次数 / 超时阈值,避免长时间自旋占用 CPU。
3. 线程池的核心线程自旋(keep-alive 机制)
Java 线程池(ThreadPoolExecutor)的核心线程,在无任务时不会立即退出,而是通过 “自旋等待” 保持存活:
核心逻辑: 核心线程 执行完任务后,会进入 BlockingQueue.take() 方法,该方法底层通过自旋 + LockSupport.park () 实现 “等待任务”(无任务时阻塞,有任务时唤醒);
非核心线程:空闲时间超过 keepAliveTime 后会被销毁,核心线程则一直自旋等待新任务(直到线程池关闭)。
特点:自旋 + 阻塞结合,既保证核心线程不退出,又不会无意义占用 CPU(无任务时阻塞)。
二、并发框架中的自旋:为高并发 / 低延迟设计
一些高性能并发框架(如 Disruptor、Netty)会直接暴露自旋策略,让开发者根据场景选择,核心目标是 “极致低延迟”。
1. Disruptor 的等待策略(自旋的典型应用)
Disruptor 作为高性能无锁框架,其 WaitStrategy(等待策略)中包含多种自旋实现,用于消费者等待生产者的事件:
BusySpinWaitStrategy(纯自旋):无限自旋,不释放 CPU,延迟最低但 CPU 占用100%,适合金融交易等低延迟场景;YieldingWaitStrategy(自旋 + 让步):自旋时调用 Thread.yield(),让渡 CPU给同优先级线程,平衡延迟和 CPU 占用;SleepingWaitStrategy(自旋 + 短暂睡眠):自旋几次后调用LockSupport.parkNanos(1),进一步降低 CPU 占用。
示例(BusySpinWaitStrategy 核心逻辑):
public class BusySpinWaitStrategy implements WaitStrategy {
@Override
public long waitFor(long sequence, Sequence cursor, Sequence dependentSequence, SequenceBarrier barrier) {
// 纯自旋,直到有新事件就绪
while (sequence > dependentSequence.get()) {
// 空循环,不释放 CPU
}
return sequence;
}
}
2. Netty 的 NIO 事件循环(自旋 + Selector 轮询)
Netty 基于 Java NIO 封装,其 NioEventLoop(事件循环)的核心是 “自旋 + Selector 轮询”:
核心逻辑:NioEventLoop 线程会持续循环,调用 Selector.select() 监听 Channel 就绪事件;若暂无就绪事件,会通过自旋 + 短暂睡眠(Thread.sleep(1))避免空耗 CPU;
特点:自旋是 “非阻塞轮询” 的补充,确保事件循环的高效性,同时避免 CPU 飙升。
三、其他隐式自旋场景:开发者间接接触
除了上述核心场景,Java 中还有一些隐式的自旋逻辑,开发者可能不会直接感知,但实际运行中存在:
LockSupport.park () 的底层自旋
LockSupport.park() 是 Java 阻塞线程的核心方法,其底层实现(如 Linux 下依赖 pthread_cond_wait)会包含 “自旋检查中断状态” 的逻辑:
线程被 park 后,若收到 unpark 信号或被中断,会通过自旋快速响应,避免阻塞状态的切换开销。
并发集合的自旋优化
Java 中的并发集合(如 ConcurrentHashMap、CopyOnWriteArrayList),在某些操作(如扩容、元素插入)中会通过自旋 CAS 确保线程安全:
示例:ConcurrentHashMap 的 putVal() 方法,插入元素时会自旋尝试获取桶锁,成功则插入,失败则重试(或扩容)。
JVM 层面的自旋(编译优化)
JVM 的即时编译器(JIT)会对循环代码进行优化,可能将 “阻塞等待” 优化为 “自旋等待”(如对 synchronized 块的循环调用),减少线程切换开销。
四、自旋的核心特点与应用原则
1. 所有自旋场景的共同特点
「短等待优先」:自旋的设计前提是 “等待时间极短”(几微秒 / 毫秒),用 CPU 忙等换取阻塞 / 唤醒的高开销;
「可控性」:有意义的自旋都会设置 “退出条件”(如 CAS 成功、锁释放、事件就绪),避免无限循环;
「场景适配」:自旋仅用于 “低延迟、短等待” 场景(如 CAS 操作、锁竞争、事件等待),不适合 “长时间等待”(如 IO 操作、远程调用)。
2. 开发者的应用原则
无需手动写自旋:日常开发中,直接用 JDK 提供的原子类、锁、线程池即可,底层已优化自旋逻辑;
避免无意义自旋:不要手动写 while(true) 无限循环(无退出条件),会导致 CPU 100% 占用;
框架场景按需选择:使用 Disruptor、Netty 等框架时,根据 “延迟需求” 选择自旋策略(如低延迟选 BusySpin,平衡 CPU 选 Yielding)。
总结
Java 中的自旋并非 “独立 API”,而是贯穿于底层机制、并发工具、高性能框架的优化手段,核心体现为:
底层:CAS 原子操作、锁机制的自旋优化、线程池核心线程等待;
框架:Disruptor 的等待策略、Netty 的事件循环;
隐式:LockSupport 底层、并发集合、JIT 编译优化。
其本质是 “用 CPU 短时间忙等换效率”,是 Java 实现高并发、低延迟的关键底层支撑,开发者无需手动实现,但理解其应用场景能更好地设计高性能并发程序。
