【Java杂谈】fail-fast与fail-safe

fail-fast与fail-safe

fail-fast

ArrayList的Iterator遍历是fail-fast的,直接在原来的数组上遍历,当发现原来的数组结构改变时会抛出ConcurrentModificationException异常。

ArrayList中有一个int型成员变量modCount,ArrayList的Iterator中保存了一个int型变量expectedModCount默认值为此时ArrayList的modCount。当调用ArrayList的add()、remove()和clear()方法时,会改变modCount的值。 Iterator在遍历的过程中,当执行迭代器的next()或remove()方法时会先检查expectedModCount是否等于modCount,以此作为判断结构改变的依据。

ArrayList中的add()、remove()和clear()

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}
private void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        System.arraycopy(es, i + 1, es, i, newSize - i);
    es[size = newSize] = null;
}
//清空所有元素
public void clear() {
    modCount++;
    final Object[] es = elementData;
    for (int to = size, i = size = 0; i < to; i++)
        es[i] = null;
}

Iterator中的next()和remove()

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];
}
public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

fail-safe

CopyOnWriteArrayList的Iterator遍历是fail-safe的,会在副本上遍历。虽然不会产生ConcurrentModification Exception异常,但不能保证数据是最新的。

当在CopyOnWriteArrayList集合上做修改时,比如调用**set()、add()和remove()**方法时,会先获取锁,保证同一时间只能有一个线程对集合做修改。然后会先将原来的数组拷贝一份,并在拷贝的数组上做修改,最后再将原来的数组替换为修改后的数组。

因为CopyOnWriteArrayList保证了多线程之间的同步修改,所以不存在线程安全问题,但是缺点也很明显:

  • 需要占用额外的空间,并且写入的效率比较低
  • 不能保证数据的实时性
public E set(int index, E element) {
    synchronized (lock) {
        Object[] es = getArray();
        E oldValue = elementAt(es, index);

        if (oldValue != element) {
            //拷贝一份
            es = es.clone();
            es[index] = element;
            //替换旧数组
            setArray(es);
        }
        return oldValue;
    }
}
public boolean add(E e) {
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        es = Arrays.copyOf(es, len + 1);
        es[len] = e;
        setArray(es);
        return true;
    }
}
public E remove(int index) {
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        E oldValue = elementAt(es, index);
        int numMoved = len - index - 1;
        Object[] newElements;
        if (numMoved == 0)
            newElements = Arrays.copyOf(es, len - 1);
        else {
            newElements = new Object[len - 1];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index + 1, newElements, index,
                             numMoved);
        }
        setArray(newElements);
        return oldValue;
    }
}

总结

fail-fast不允许在遍历时改变集合的结构,因为这种方式的遍历是直接遍历原数据。

fail-safe则允许在遍历时改变集合的结构,因为这种遍历是建立在原数据副本之上的,副本相当于集合在某一时刻的“快照”。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值