一些容易忘掉的知识点 - Java

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
2
3
4
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

ConcurrentHashMap,key 或 value 为 null 直接抛出空指针异常!

1
2
3
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
......

这么做的原因:

会有一个问题,当通过 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。

阅读更多