线程安全的糟糕之处在于,错误很少发生并且难以重现。Java 在很大程度上将线程的处理委托给操作系统,因此当多个任务同时运行时,程序员无法准确控制线程如何以及何时被操作系统暂停和切换。观察到的各种错误的频率可能会有所不同,具体取决于 CPU 是单核还是双核或四核。
并发错误很少会“使系统崩溃”,更可能的问题是状态不一致。但是,如果您正在使用 Iterator 遍历集合,而另一个 Thread 修改该集合,那么您将获得ConcurrentModificationException。例如:
Set<String> words; //a field that can be accessed by other threads.
// may throw ConcurrentModificationException
public ArrayList<String> unsafeIteration() {
ArrayList<String> longWords = new ArrayList<>();
for(String word : words) {
if(word.length()>4)
longWords.add(word);
}
return longWords ;
}
Iterator 的实现尝试检测它正在迭代的集合的并发修改,但这只是“快速失败”的最大努力尝试。通过抛出异常让程序失败比出现不可预测的行为要好得多。javadocs 做出此免责声明:
请注意,不能保证快速失败的行为,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬保证。快速失败操作会尽最大努力抛出 ConcurrentModificationException。因此,编写一个依赖此异常来确保其正确性的程序是错误的:ConcurrentModificationException 应该仅用于检测错误。
如果我们只是从一个集合中读取数据,get
那么我们不会看到这个异常,但我们确实会冒着状态不一致的风险。有时这不是需要修复的问题。如果只有一个线程写入一个字段,并且所有线程总是看到该字段中的最新值并不重要,我认为只要你远离迭代器就可以了。