0%

活性失败、快速失败和安全失败

活性失败

在多线程并发的情况下,线程A和线程B都使用了变量V,如果线程A修改了共享变量V,线程B并不知道共享变量V已被线程A修改,这种情况就叫做活性失败。

活性失败是在并发编程中必须解决的问题,我们需要让各个线程之间对共享变量具有happen-before关系,通常使用Volatile或者加锁的方式来解决。

Volatile是Java提供的一种弱同步机制,当变量被volatile修饰后编译器不会将该变量的操作与其他内存操作进行重排序,使得任一线程对volatile变量进行了修改操作后,其他线程能立即知道修改的值。

快速失败(fail-fast)

当我们对部分集合(如ArrayList,HashMap)进行迭代操作时,如果存在对集合的添加或删除操作,将会导致迭代抛出ConcurrentModificationException异常,这就叫做快速失败。

因为快速失败检测的是元素总数或变化次数,所以集合中的元素是对象,当我们元素对象的属性进行修改操作时不会报错。
例如ArrayList进行迭代是判断的是元素个数,当迭代进行至倒数第二个元素时,我们进行了删除元素操作也不会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
......
private class Itr implements Iterator<E> {
...
public boolean hasNext() {
// 移除元素后这里返回false,不调用next()也就不会报错
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
}
......
}

这里虽然不会抛出异常,但其实是最后一个元素没有被遍历到,这种丢数据BUG怕是更隐晦,要是线上出现问题怕是会更酸爽!
HashMap迭代时判断下一个元素时,检测的是变化(put、remove等操作)次数,判断modCount变量是否为开始遍历时的值。ArrayList使用ForEach遍历会先检测元素个数再检测变化次数。

  • 不要在非安全性的集合遍历中修改集合的结构(增加、删除[迭代器自身的remove除外]或更新集合中的元素)。  

安全失败(fail-safe)

对于ConcurrentHashMap这个并发安全的集合,使用的是弱一致性迭代器Iterator,在遍历过程中并不对原集合所作的修改进行校验,不会抛出ConcurrentModificationException异常,这种机制叫做安全失败。

ConcurrentHashMap迭代所遍历的不是迭代时刻的快照,而是迭代各个Segement的真实数据。若迭代期间有数据发生变更,如果变更的是已经遍历的Segement则迭代过程不再体现这个变化,但如果变化发生在未遍历的Segement,则本次迭代会体现出到这个元素的变化。