为何字节序列生成器不能用get()替代incrementAndGet()?线程安全解析
咱们一步步拆解问题,先搞清楚两个实现的核心差异,以及你改的地方为啥会引入线程安全问题。
首先明确需求:我们需要一个循环生成byte范围(-128到127)的序列生成器,多线程环境下必须保证每个线程拿到的值唯一且符合范围。
先看线程安全的ByteGenerator2是怎么工作的
当counter.incrementAndGet()超过Byte.MAX_VALUE(127)时,线程进入同步块:
- 首先再次调用
counter.incrementAndGet():这是关键操作——不管外面的线程之前做了什么,进入同步块后先原子性地递增一次,确保当前线程拿到的是最新、唯一的值。 - 如果递增后的值还是超过127(说明是第一个进入同步块的线程,counter还没被重置),就把counter设为
INITIAL_VALUE+1(也就是Byte.MIN_VALUE=-128),返回这个值作为循环的起点。 - 后续进入同步块的线程,因为counter已经被重置成-128,再次
incrementAndGet()会得到-127,这个值在byte范围内,直接返回即可。
这种方式保证了每个进入同步块的线程都会原子性地获取下一个新值,不会出现重复,也不会有范围外的问题。
再看你修改后的ByteGeneratorNotThreadSafe哪里出了问题
你把同步块内的incrementAndGet()改成了get(),还移除了初始值的+1操作,这直接引入了竞态条件,导致两个严重的线程安全问题:
问题1:多个线程可能拿到重复的byte值
假设有两个线程A和B,同时执行到next = counter.incrementAndGet(),此时counter的值是127,所以两个线程的next都是128(超过了Byte.MAX_VALUE),然后都进入同步块等待锁:
- 线程A先拿到锁,执行
next = counter.get()(得到128),满足next > Byte.MAX_VALUE,于是把counter设为INITIAL_VALUE(-129),返回(byte)-129(由于byte的溢出规则,这个值实际是127)。 - 线程B拿到锁后,执行
next = counter.get()(此时counter是-129),这个值不大于127,直接返回(byte)-129(同样是127)。
结果就是A和B都返回了127,出现了重复值,完全违背了序列生成器的要求。
问题2:生成不符合预期的范围值
INITIAL_VALUE是Byte.MIN_VALUE-1(-129),当你直接返回(byte)INITIAL_VALUE时,这个值会因为byte的溢出变成127,和Byte.MAX_VALUE重复。而原本的安全实现返回的是INITIAL_VALUE+1(-128),也就是byte的最小值,这才是循环序列的正确起点。
核心问题:同步块内丢失了原子性递增逻辑
在安全实现里,同步块内的incrementAndGet()确保了每个进入同步块的线程都会原子性地获取下一个值,即使counter已经被重置,也能拿到新的递增后的值。而你改成get()后,只是读取当前值,没有做递增,导致多个等待锁的线程会读取到同一个值,进而返回重复的byte。
总结一下:你的修改破坏了同步块内的原子性递增逻辑,让多个线程可能共享同一个counter值,从而产生重复输出,同时引入了范围溢出问题,最终导致整个生成器不再线程安全。
内容的提问来源于stack exchange,提问作者A. T

