AtomicLong按指定delta值递减的正确实现与差异场景分析
问题解答
两种方案的正确性结论
- 方案1存在线程安全缺陷,高并发场景下无法保证原子递减的预期效果
- 方案2是正确的原子递减实现
方案1失效的场景原因
AtomicLong.getAndUpdate()的设计逻辑是:内部先获取当前最新值作为入参传给传入的lambda函数,计算新值后通过CAS尝试更新,CAS失败则自动重试整个过程,重试时会重新获取最新值传入lambda。
但方案1的lambda函数没有使用方法传入的当前值l,反而主动调用了count.get()获取值,这个额外的get操作和整个更新流程没有原子性绑定:
比如两个线程同时执行递减操作,当前count初始值为10,两个线程都要减1。线程A的lambda拿到入参l=10,此时还没计算,线程B已经完成更新把count改成了9,线程A的lambda里执行
count.get()拿到的就是9,最终计算结果是9-1=8,相当于两个线程各减1,最终结果是8而不是预期的10-2=9,出现计数少减的问题。
高并发场景下,大量线程同时调用该方法时,方案1的计数偏差会非常明显,完全无法满足原子性要求。如果要使用getAndUpdate实现,正确写法应该是:
count.getAndUpdate(l -> l - decrementBy);
方案2的取负溢出问题分析
Java中long是64位有符号整数,取值范围是[-2^63, 2^63 - 1],只有当传入的decrementBy等于Long.MIN_VALUE(即-2^63)时,-decrementBy的结果会因为超过正long的最大值溢出,结果还是Long.MIN_VALUE。
但这个溢出属于业务逻辑层面的边界问题:
- 逻辑上
decrementBy代表要递减的数值,本身就不应该传入负数,更不会出现传入Long.MIN_VALUE的场景 - 即便真的传入该值,这个溢出是Java有符号数运算的统一规则,和
getAndAdd的用法无关,换任何实现方式都会遇到同样的问题 - 正常业务场景下,只要
decrementBy的取值范围符合预期,该写法没有任何问题
内容的提问来源于stack exchange,提问作者Ping
相关产品推荐
相关产品推荐

