为何无锁场景下BitSet执行set()会出现(false,true)或(true,false)结果?
为什么BitSet多线程无锁set会出现读取不一致?
核心原因是BitSet的set()方法并非原子操作——哪怕底层位运算本身是原子的,整个操作流程仍存在竞态条件。
具体分析步骤:
BitSet底层用long[] words存储位信息,bit1和bit2都落在同一个long元素(words[0])里。set(int bitIndex)的核心逻辑大致如下:
int wordIndex = bitIndex >> 6; // 计算对应long数组下标 long oldValue = words[wordIndex]; long newValue = oldValue | (1L << bitIndex); words[wordIndex] = newValue;
(实际源码包含扩容检查,但核心读写逻辑一致)
当两个线程同时操作同一个long元素时,会发生以下典型时序:
- 线程1读取
words[0]的初始值0 - 线程2也读取
words[0]的初始值0 - 线程1计算得到
0 | (1<<1) = 2,并写回words[0] - 线程2计算得到
0 | (1<<2) = 4,并写回words[0]
最终words[0]的值是4,导致bit1为false、bit2为true;反之如果线程2先写回,就会出现bit1为true、bit2为false的情况。
关键误区:
你以为位运算(|=)是原子的就不会有问题,但|=本质上是读-改-写的组合操作,这三步整体没有同步保护,多个线程并发执行时会互相覆盖修改。另外需要注意,BitSet的words数组并非volatile,线程间内存可见性无法保证,但在这个场景里,第三个线程等待前两个线程终止后读取,核心问题还是竞态条件导致的覆盖,而非可见性。
解决方案:
- 对BitSet的所有操作加锁(比如
synchronized块) - 使用
Collections.synchronizedBitSet()包装BitSet,获得线程安全的实现 - 如果是JDK 9+,可以考虑使用
VarHandle实现原子的位修改操作
内容的提问来源于stack exchange,提问作者ng.newbie
相关产品推荐
相关产品推荐

