1 Java 基础
1.1 快速失败(fail-fast)和安全失败(fail-safe)机制
快速失败(fail-fast
)是 Java 集合的一种错误检测机制。
在使用迭代器对集合进行遍历的时候,在多线程下操作非安全失败(fail-safe
)的集合类可能就会触发 fail-fast
机制,导致抛出 ConcurrentModificationException
异常。
在单线程下,如果在遍历过程中对集合对象的内容进行了修改的话也会触发 fail-fast
机制。
例如:多线程下,如果线程 1 正在对集合进行遍历,此时线程 2 对集合进行修改(增加、删除、修改),或者线程 1 在遍历过程中对集合进行修改,都会导致线程 1 抛出
ConcurrentModificationException
异常。
原理
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变 modCount 的值。
每当迭代器使用 hashNext ()/next () 遍历下一个元素之前,都会检测 modCount 变量是否为 expectedmodCount 值,是的话就返回遍历;否则抛出异常,终止遍历。
采用安全失败(fail-safe
)机制的集合容器(JUC 包下的容器),在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历,可以在多线程下并发使用,并发修改。所以,在遍历过程中对原集合所作的修改并不能被迭代器检测到,故不会抛 ConcurrentModificationException
异常。
1.2 HashMap 与 ConcurrentHashMap 对 null 值的处理问题
结论:HashMap 的 key 和 value 都可以为 null,ConcurrentHashMap 的均不可以为 null
HashTable 也是不允许 key 和 value 为空的,原因和 ConcurrentHashMap 一致
上源码!
HashMap,当 key 为 null 的时候,hash 值为 0,后续的 put 方法中也没有判断 value 是否为 null 的代码。
1 | static final int hash(Object key) { |
ConcurrentHashMap,key 或 value 为 null 直接抛出空指针异常!
1 | final V putVal(K key, V value, boolean onlyIfAbsent) { |
这么做的原因:
会有一个问题,当通过 get(k)
获取对应的 value 时,如果获取到的是 null,无法判断它是 put(k,v)
的时候 value 为 null,还是这个 key 从来没有做过映射。
HashMap 多用于非并发场景中,可以通过 contains(key)
来做这个判断。而在多用于并发场景中的 ConcurrentHashMap 中调用 m.contains(key)
判断然后再 m.get(key)
时,m 可能已经被其他线程修改而不同了。为了避免这种二义性,ConcurrentHashMap 在设计之初就直接令 key 和 value 都不为 null。