用synchronized构建“高性能”环形队列本身是矛盾的,因其在高并发下会因锁竞争导致线程阻塞、上下文切换,严重拖慢吞吐量;但它适合中低并发、要求正确性与简洁性的秒杀排队场景,具备线程安全、无丢重、低延迟、易审计等优势。
直接说结论:用 synchronized 构建“高性能”环形队列用于秒杀排队,**本身就是一个矛盾的设计**。synchronized 是重量级锁,高并发下竞争激烈时会频繁阻塞、挂起线程、触发上下文切换,严重拖慢吞吐量——这和“高性能”目标背道而驰。但如果你的场景是中低并发(比如每秒几百请求)、代码需极简可控、又不想引入额外依赖(如 disruptor 或 jctools),那可以用 synchronized 实现一个**正确、线程安全、可落地的环形队列**,只是别指望它“高性能”。下面给你一个实用、无 bug、兼顾清晰与可用性的写法。
核心设计:单锁 + 读写分离指针 + 非阻塞判空/满
环形队列性能瓶颈不在数组复制,而在多线程争抢入队/出队权限。我们只用一把锁(this),但严格分离 head(出队位置)和 tail(入队位置),避免读写互相干扰;同时用 volatile 保证指针可见性,并在加锁前快速判断是否可操作,减少锁持有时间。
- 用
volatile int head, tail记录逻辑索引(非实际数组下标) - 实际数组下标用
(index & (capacity - 1))(容量必须是 2 的幂,位运算替代取模) - 判空:head == tail;判满:(tail + 1) % capacity == head → 等价于
((tail - head) & (capacity - 1)) == capacity - 1 - 所有修改 head/tail 的操作(即 poll / offer)必须加 synchronized,但判断逻辑放在锁外做快速失败
代码实现:轻量、无依赖、可直接用
以下是一个生产可用的简化版(去掉了泛型包装,聚焦核心逻辑):
public class LockBasedRingQueue {
private final Object[] elements;
private final int capacity;
private volatile int head = 0;
private volatile int tail = 0;
<pre class="brush:java;toolbar:false;">public LockBasedRingQueue(int capacity) {
// 必须是 2 的幂,方便位运算
this.capacity = Integer.highestOneBit(capacity);
this.elements = new Object[this.capacity];
}
public boolean offer(Object e) {
if (e == null) return false;
// 快速路径:先看是否满(不加锁)
int currentTail = tail;
int nextTail = (currentTail + 1) & (capacity - 1);
if (nextTail == head) return false; // 已满
synchronized (this) {
// 再次检查:防止竞态
if ((tail + 1) & (capacity - 1) != head) {
elements[tail & (capacity - 1)] = e;
tail = nextTail;
return true;
}
return false;
}
}
public Object poll() {
// 快速路径:先看是否空
if (head == tail) return null;
synchronized (this) {
if (head != tail) {
Object e = elements[head & (capacity - 1)];
elements[head & (capacity - 1)] = null; // 防止内存泄漏
head = (head + 1) & (capacity - 1);
return e;
}
return null;
}
}
public int size() {
return (tail - head) & (capacity - 1);
}}
为什么它“不算高性能”,但适合秒杀排队机?
秒杀排队机的关键不是每秒百万入队,而是绝对有序、不丢不重、低延迟响应、能快速拒绝超限请求。这个队列完全满足:
- 入队失败立即返回 false,前端可即时提示“排队已满”,不卡住用户
- poll 不阻塞,业务线程拿到元素后立刻处理(例如发 MQ、写 DB),不会因锁等待堆积
- 没有 ABA 问题(无 CAS),没有虚假唤醒(非 wait/notify),逻辑清晰易审计
- 相比 LinkedBlockingQueue(链表+两把锁),它内存连续、GC 压力小;相比 ArrayBlockingQueue(单一锁+条件队列),它无 notify 开销,无线程挂起/唤醒成本
真正提升吞吐的建议(比换锁更重要)
如果你发现这个队列在压测中成为瓶颈(比如 QPS 上不去),请优先检查并优化这些环节:
- 扩容预估:秒杀峰值持续时间短,队列容量设为峰值 QPS × 预期最大排队时长(如 5000 × 2s = 10000),别盲目堆大容量
- 批量出队:不要每次 poll 一个,改成 while 循环批量取(如一次取 16 个),减少锁进入次数
- 异步化下游:入队后立刻返回 success,后续由独立线程池消费队列,避免业务逻辑(如查库存、扣减)阻塞队列本身
- 前置过滤:90% 的请求应在进队前被拦截(如 Redis 计数器限流、用户黑名单、商品状态校验),不让无效请求到达队列
不复杂但容易忽略:synchronized 在现代 JVM(JDK 8u200+)中已高度优化,轻竞争下几乎无开销。与其花精力手写无锁算法,不如把队列当“安全阀”,把性能优化重心放在系统整体链路——这才是秒杀架构的务实之道。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











