应使用arraydeque替代stack:前者无锁、循环数组、位运算索引、api纯净,吞吐量高3–5倍;后者继承vector导致同步冗余、接口污染、设计过时。

Stack的synchronized方法真会拖慢单线程性能
Java Stack 继承自 Vector,而 Vector 的每个 public 方法(比如 push()、pop()、size())都带 synchronized 修饰。这意味着哪怕你只在单线程里压入 10 万个整数,每次调用都要进锁、检查 monitor、释放锁——这些动作不干任何业务,纯属冗余开销。
- 实测显示:万级元素压栈场景下,
ArrayDeque吞吐量通常是Stack的 3–5 倍 - 锁不是“没用”,而是“不该由数据结构默认承担”——多数业务本就不需要线程安全,强制同步反而掩盖了真实并发意图
- 如果你真需要线程安全栈,应该显式包装:
Collections.synchronizedDeque(new ArrayDeque()),而不是依赖一个过时类的伪安全
ArrayDeque底层无锁 + 循环数组 = 稳定O(1)栈操作
ArrayDeque 不仅没锁,它的数组是容量为 2 的幂次方的循环数组,索引计算靠位运算:(head - 1) & (elements.length - 1),比取模快得多;扩容也按 2 倍增长,复制时分两段搬移,保持逻辑顺序。
-
Stack初始容量 10,扩容需System.arraycopy整个数组;ArrayDeque初始容量 8,扩容更轻量 -
pop()和push()都落在数组头部(head指针),避免了Stack中因继承Vector而可能触发的尾部扩容误判 - 它不允许存
null,这看似限制,实则消除了空值歧义(比如pop()返回null就一定表示空栈,而非存了个null)
别再用add(int, E)往Stack中间插元素了
Stack 因为继承 Vector,暴露了 add(int, E)、remove(int)、set(int, E) 等完全违背 LIFO 语义的方法。这不是“功能多”,是接口污染。
- 下面这段代码能编译通过,但已经不是栈了:
Stack<Integer> s = new Stack<>();<br>s.push(1); s.push(2);<br>s.add(0, 999); // 插到最底下<br>s.remove(1); // 删中间一个<br>// 结果:[999, 2] —— LIFO 被彻底破坏
-
ArrayDeque实现的是Deque接口,只提供push()/pop()、offerFirst()/pollFirst()等契约明确的操作,从 API 层杜绝误用 - 声明类型应写成
Deque<integer> stack = new ArrayDeque();</integer>,而不是Stack<integer></integer>,这样连 IDE 都不会提示那些危险方法
为什么官方文档明确说“should be used in preference to this class”
JavaDoc 对 Stack 的描述里有句原话:“A more complete and consistent set of LIFO stack operations is provided by the Deque interface... which should be used in preference to this class.” 这不是建议,是定性——Stack 是遗留类(legacy class),设计上已认定不合理。
立即学习“Java免费学习笔记(深入)”;
- 它违反接口隔离原则(ISP)、组合优于继承、职责单一等基础设计原则
- JDK 1.0 就存在,20 多年来没大改,只为向后兼容;而
ArrayDeque自 Java 6 引入,持续优化至今 - LeetCode、Spring、Guava 等主流项目中,所有新写的栈逻辑几乎全用
ArrayDeque,Stack只出现在老代码或教学示例里
真正容易被忽略的点是:很多人以为“只要不用多线程就无所谓同步开销”,但现代 JVM 虽然做了锁优化(如偏向锁、轻量级锁),可 synchronized 仍会插入内存屏障、影响指令重排,对高频栈操作(比如解析表达式、DFS 递归替代)来说,这点损耗会累积放大。选 ArrayDeque 不只是图快,更是让代码意图清晰、行为可预测。










