上一篇用AtomicIntegerArray 为固定槽位提供原子更新,也说明了整组读取的边界。今天继续看AtomicLongArray:下载字节数、累计用量等统计可能很快超过int范围,换成long以后,怎样可靠累加,又怎样在更新前检查上限?
本文适用于Java21,示例采用JDK21.0.8。重点围绕三个问题:固定槽位怎样存放long值,单个槽位怎样通过CAS执行条件更新,以及原子累加为什么仍要考虑溢出和跨槽位约束。
文章目录
-
- 一、数组结构:索引固定,元素扩大为long
- 二、常用方法:先确认返回哪一刻的值
- 三、带上限的占用:检查与写入放进同一循环
- 四、完整示例:大数累加、占用检查与溢出
- 五、数值范围扩大后,并发边界仍要保留
- [六、🧠 思维导图](#六、🧠 思维导图)
- 七、总结
一、数组结构:索引固定,元素扩大为long
AtomicLongArray 保存一组支持原子更新的long元素。长度在构造时确定;按长度构造的槽位初始为0,传入long\[\]时会复制数值。修改原数组不会同步修改原子数组。相关构造器与方法约定见Java21官方API。
可以让索引0与索引1分别对应两类请求的累计字节数。索引与业务类别的映射由应用维护,数组没有自动分组或扩容功能。
long的取值范围大于int,适合更大的累计数值。大于int上限的整数字面量需要写成3_000_000_000L;下划线帮助阅读,末尾的L明确类型。Java整数类型和范围见语言规范的整数类型章节。
二、常用方法:先确认返回哪一刻的值
| 方法 | 操作对象 | 返回内容 |
|---|---|---|
get(i) |
索引i | 当前值 |
addAndGet(i, delta) |
索引i原子累加 | 更新后的值 |
getAndAdd(i, delta) |
索引i原子累加 | 更新前的值 |
compareAndSet(i, expected, next) |
索引i条件更新 | 是否成功 |
updateAndGet(i, function) |
索引i函数式更新 | 更新后的值 |
读取以后再调用set,会把"读取"和"写入"拆成两个独立动作。多个线程可能读到同一个旧值,之后互相覆盖。需要可靠累加时,使用类提供的原子累加方法。
CAS只检验指定索引当前是否等于expected。失败后重新读取并重新检查业务条件,才能围绕最新状态作出判断。
三、带上限的占用:检查与写入放进同一循环
假设一个槽位记录已占用字节数,上限为100亿,当前为60亿。申请增加30亿可以接受,再申请20亿则应拒绝。
更新流程可以组织为:读取old,检查本次增加是否越界,再用CAS将old改成next。竞争线程先更新了这个槽位时,CAS失败,循环重新读取并判断。
判断采用delta > limit - old,并先排除负值和old > limit。这样可以避免为检查上限而先计算old + delta,使检查表达式本身发生溢出。通过条件后,old与delta的和不会超过非负long上限limit。

图中只解释一个槽位的条件更新。CAS成功表示该次数值占用已提交,之后的网络发送、文件写入或其他操作仍可能失败,应用需要单独设计后续处理。
四、完整示例:大数累加、占用检查与溢出
下面的程序让四个线程更新两个槽位。每个线程累加2000次,每次增加150万,两个线程共用同一索引;等待所有线程结束后,再读取结果。随后演示带上限的占用,以及两种不同的溢出处理方式。
保存为AtomicLongArrayDemo.java:
java
import java.util.concurrent.atomic.AtomicLongArray;
public class AtomicLongArrayDemo {
private static boolean tryReserve(AtomicLongArray used, int index,
long delta, long limit) {
if (delta < 0 || limit < 0) {
throw new IllegalArgumentException("negative delta or limit");
}
while (true) {
long old = used.get(index);
if (old < 0 || old > limit || delta > limit - old) {
return false;
}
if (used.compareAndSet(index, old, old + delta)) {
return true;
}
}
}
public static void main(String[] args) throws InterruptedException {
long[] original = {3_000_000_000L};
AtomicLongArray copy = new AtomicLongArray(original);
original[0] = 7;
System.out.println("copied=" + copy.get(0));
AtomicLongArray bytes = new AtomicLongArray(2);
Thread[] workers = new Thread[4];
for (int n = 0; n < workers.length; n++) {
int index = n % bytes.length();
workers[n] = new Thread(() -> {
for (int round = 0; round < 2_000; round++) {
bytes.addAndGet(index, 1_500_000L);
}
});
workers[n].start();
}
for (Thread worker : workers) {
worker.join();
}
System.out.println("bytes=" + bytes);
System.out.println("reserve-3B=" +
tryReserve(bytes, 0, 3_000_000_000L, 10_000_000_000L));
System.out.println("reserve-2B=" +
tryReserve(bytes, 0, 2_000_000_000L, 10_000_000_000L));
System.out.println("slot-0=" + bytes.get(0));
AtomicLongArray wrapped = new AtomicLongArray(
new long[]{Long.MAX_VALUE - 1});
System.out.println("wrapped=" + wrapped.addAndGet(0, 5));
AtomicLongArray checked = new AtomicLongArray(
new long[]{Long.MAX_VALUE - 1});
try {
checked.updateAndGet(0, old -> Math.addExact(old, 5));
} catch (ArithmeticException e) {
System.out.println("checked=overflow, current=" + checked.get(0));
}
}
}
运行:
bash
javac -encoding UTF-8 AtomicLongArrayDemo.java
java AtomicLongArrayDemo
本次实际输出:
text
copied=3000000000
bytes=[6000000000, 6000000000]
reserve-3B=true
reserve-2B=false
slot-0=9000000000
wrapped=-9223372036854775805
checked=overflow, current=9223372036854775806
复制与累加。第一行表明原数组后来改成7,副本仍保留30亿。两个槽位最终各为60亿,已经超过int的正数范围。
条件更新。第一次增加30亿成功,使索引0达到90亿;下一次增加20亿会超过100亿上限,因此返回false,数值保持90亿。
溢出处理 。普通long加法会回绕,原子累加同样遵循long算术。Math.addExact检测到溢出后抛出ArithmeticException;示例中的函数没有返回新值,因此没有提交该次更新,槽位保留原值。精确算术方法的约定见Math官方API。
五、数值范围扩大后,并发边界仍要保留
单个索引的条件更新。tryReserve保护的条件只围绕一个槽位。如果业务要求从索引0扣减、同时给索引1增加,两次原子操作之间仍存在中间状态。跨槽位守恒关系需要所有相关读写共同遵循锁协议,或者采用整组不可变状态与AtomicReference整体替换。
读取总量的时机。本例先等待线程全部结束,再读取两个槽位。若更新仍在进行,逐个get后相加得到的是分次观察,不能据此认定拿到了某一时刻的整体快照;总量相加本身也可能溢出。
函数保持无副作用。updateAndGet等方法在竞争期间可能重新应用函数。示例的Math.addExact只从旧值计算新值;不要把通知发送、外部计数或扣款放进更新函数。函数重试约定见前面的AtomicLongArray方法说明。
内存语义按方法区分。常规get、set与CAS有各自的内存效果,类还提供plain、opaque、acquire和release等变体。本文先使用常规方法,不把全部访问方式当成相同语义。
容量与索引。long扩大了数值范围,数组仍有固定长度。索引越界、业务类别映射错误、累计数值回绕,都需要由应用处理。CAS的成功也不证明外部业务动作已经完成。
六、🧠 思维导图
#mermaid-svg-tE076CFQX3YZp4OM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tE076CFQX3YZp4OM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tE076CFQX3YZp4OM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tE076CFQX3YZp4OM .error-icon{fill:#552222;}#mermaid-svg-tE076CFQX3YZp4OM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tE076CFQX3YZp4OM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tE076CFQX3YZp4OM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tE076CFQX3YZp4OM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tE076CFQX3YZp4OM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tE076CFQX3YZp4OM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tE076CFQX3YZp4OM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tE076CFQX3YZp4OM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tE076CFQX3YZp4OM .marker.cross{stroke:#333333;}#mermaid-svg-tE076CFQX3YZp4OM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tE076CFQX3YZp4OM p{margin:0;}#mermaid-svg-tE076CFQX3YZp4OM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tE076CFQX3YZp4OM .cluster-label text{fill:#333;}#mermaid-svg-tE076CFQX3YZp4OM .cluster-label span{color:#333;}#mermaid-svg-tE076CFQX3YZp4OM .cluster-label span p{background-color:transparent;}#mermaid-svg-tE076CFQX3YZp4OM .label text,#mermaid-svg-tE076CFQX3YZp4OM span{fill:#333;color:#333;}#mermaid-svg-tE076CFQX3YZp4OM .node rect,#mermaid-svg-tE076CFQX3YZp4OM .node circle,#mermaid-svg-tE076CFQX3YZp4OM .node ellipse,#mermaid-svg-tE076CFQX3YZp4OM .node polygon,#mermaid-svg-tE076CFQX3YZp4OM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tE076CFQX3YZp4OM .rough-node .label text,#mermaid-svg-tE076CFQX3YZp4OM .node .label text,#mermaid-svg-tE076CFQX3YZp4OM .image-shape .label,#mermaid-svg-tE076CFQX3YZp4OM .icon-shape .label{text-anchor:middle;}#mermaid-svg-tE076CFQX3YZp4OM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tE076CFQX3YZp4OM .rough-node .label,#mermaid-svg-tE076CFQX3YZp4OM .node .label,#mermaid-svg-tE076CFQX3YZp4OM .image-shape .label,#mermaid-svg-tE076CFQX3YZp4OM .icon-shape .label{text-align:center;}#mermaid-svg-tE076CFQX3YZp4OM .node.clickable{cursor:pointer;}#mermaid-svg-tE076CFQX3YZp4OM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tE076CFQX3YZp4OM .arrowheadPath{fill:#333333;}#mermaid-svg-tE076CFQX3YZp4OM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tE076CFQX3YZp4OM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tE076CFQX3YZp4OM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tE076CFQX3YZp4OM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tE076CFQX3YZp4OM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tE076CFQX3YZp4OM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tE076CFQX3YZp4OM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tE076CFQX3YZp4OM .cluster text{fill:#333;}#mermaid-svg-tE076CFQX3YZp4OM .cluster span{color:#333;}#mermaid-svg-tE076CFQX3YZp4OM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tE076CFQX3YZp4OM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tE076CFQX3YZp4OM rect.text{fill:none;stroke-width:0;}#mermaid-svg-tE076CFQX3YZp4OM .icon-shape,#mermaid-svg-tE076CFQX3YZp4OM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tE076CFQX3YZp4OM .icon-shape p,#mermaid-svg-tE076CFQX3YZp4OM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tE076CFQX3YZp4OM .icon-shape .label rect,#mermaid-svg-tE076CFQX3YZp4OM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tE076CFQX3YZp4OM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tE076CFQX3YZp4OM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tE076CFQX3YZp4OM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} AtomicLongArray
固定槽位
长度固定与复制数值
long扩大计数范围
单格更新
原子累加与返回值
CAS检查上限并重试
数值边界
普通加法可能回绕
Math.addExact检测溢出
整体协议
跨槽位更新需要协议
分次读取不保证快照
七、总结
总结要点
long适合更大的累计数值。固定槽位、构造复制和原子累加的基本用法可以延续前文。返回旧值还是新值,仍要按具体方法选择。
单格条件与CAS一起提交。先检查上限,再尝试更新,竞争失败后重新判断。条件表达式和最终加法都要避免越界。
原子性与业务边界各有范围。数值回绕、跨槽位约束、整体快照和外部动作,需要应用提供额外设计;更新函数保持纯粹,重试才容易解释。
下一篇继续看AtomicReferenceArray:数组元素换成对象引用以后,身份比较、对象内部变化与原子替换怎样衔接。
👉 如果你觉得这篇文章对你有所帮助,欢迎点赞、收藏、分享!😊