对于一个数据结构,如果作者没有表明该数据结构是线程安全的,我一般都会认为不是线程安全的,因为编写高效且线程安全的数据结构是比较困难的,关于 RoaringBitmap 的线程安全问题,早在 2015 年就有用户反馈希望能够有线程安全的实现,详见该 issue: thread safety,仅有几次提交后至 2017 年后就再无下文,而 RoaringBitmap 的主线一直在演进,且在官方文档中甚至没有关于线程安全实现的介绍,以至后续在我的工程中,关于 RoaringBitmap 的并发修改使用了 ReentrantReadWriteLock 去保证多线程下的线程安全,使用读写锁是为了提高读多于写场景下的吞吐量。
对于 RoaringBitmap 主线上的代码,很容易证明不是线程安全的,我们可以写个简单的程序进行验证,本文基于 RoaringBitmap 0.9.15 版本进行验证,其 Maven 依赖声明如下:
1 | <dependency> |
编写简单的验证程序:
1 | package me.tianshuang; |
程序会抛出异常:
1 | Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0 |
我们可以稍做分析,contains
方法的源码为:
1 | /** |
contains
方法先获取 int x
数字的高十六位,用于定位 container 的索引,然后获取该 container,再判断该 container 中是否存在低十六位的值以得到 int x
是否存在于该 bitmap 的结果。在整个流程中,如果有其他线程触发了收缩则可能触发数组下标越界异常。
故在我的分布式 bitmap 服务中,采用了 ReentrantReadWriteLock 去保证多线程下 bitmap 的线程安全,在 ReentrantReadWriteLock 的官方文档中,其中举例即使用了 ReentrantReadWriteLock 去保证 TreeMap 的线程安全问题。
我们可以对上述程序进行进行简单的调整即可保证线程安全:
1 | package me.tianshuang; |
Reference
RoaringBitmap
ReadWriteLock (Java Platform SE 8 )
ReentrantReadWriteLock (Java Platform SE 8 )