fail-fast 与 fail-safe 机制有什么区别

快速失效与安全失效,是针对迭代数据结构过程出现的两种说法。

Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。

fail-fast机制,是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生若在多线程环境下使用fail-fast机制的集合,建议使用“java.util.concurrent包下的类”去取代“java.util包下的类”。

以ArraryList举例,何时出现fail-fast事件,抛出ConcurrentModificationException异常。

public abstract class AbstractListextends AbstractCollectionimplements List {

...

// AbstractList中唯一的属性

    // 用来记录List修改的次数:每修改一次(添加/删除等操作),将modCount+1

    protected transient int modCount =0;

    // 返回List对应迭代器。实际上,是返回Itr对象。

    public Iteratoriterator() {

return new Itr();

    }

// Itr是Iterator(迭代器)的实现类

    private class Itrimplements Iterator {

int cursor =0;

        int lastRet = -1;

        // 修改数的记录值。

        // 每次新建Itr()对象时,都会保存新建该对象时对应的modCount;

        // 以后每次遍历List中的元素的时候,都会比较expectedModCount和modCount是否相等;

        // 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。

        int expectedModCount =modCount;

        public boolean hasNext() {

return cursor != size();

        }

public E next() {

// 获取下一个元素之前,都会判断“新建Itr对象时保存的modCount”和“当前的modCount”是否相等;

            // 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。

            checkForComodification();

            try {

E next = get(cursor);

                lastRet =cursor++;

                return next;

            }catch (IndexOutOfBoundsException e) {

checkForComodification();

                throw new NoSuchElementException();

            }

}

public void remove() {

if (lastRet == -1)

throw new IllegalStateException();

            checkForComodification();

            try {

AbstractList.this.remove(lastRet);

                if (lastRet

cursor--;

                lastRet = -1;

                expectedModCount =modCount;

            }catch (IndexOutOfBoundsException e) {

throw new ConcurrentModificationException();

            }

}

final void checkForComodification() {

if (modCount !=expectedModCount)

throw new ConcurrentModificationException();

        }

}

...

}

在调用 next() 和 remove()时,都会执行 checkForComodification()。若 “modCount 不等于 expectedModCount”,则抛出ConcurrentModificationException异常,产生fail-fast事件。

在创建iterator的时候,modCount 与 expectedModCount 是相等的,但是何时会被修改呢?下面可以看一下ArraryList的源码。

public class ArrayList extends AbstractList

implements List, RandomAccess, Cloneable, java.io.Serializable {

...

// list中容量变化时,对应的同步函数

    public void ensureCapacity(int minCapacity) {

modCount++;

        int oldCapacity = elementData.length;

        if (minCapacity > oldCapacity) {

Object oldData[] = elementData;

            int newCapacity = (oldCapacity *3) /2 +1;

            if (newCapacity < minCapacity)

newCapacity = minCapacity;

            // minCapacity is usually close to size, so this is a win:

            elementData = Arrays.copyOf(elementData, newCapacity);

        }

}

// 添加元素到队列最后

    public boolean add(E e) {

// 修改modCount

        ensureCapacity(size +1);  // Increments modCount!!

        elementData[size++] = e;

return true;

    }

// 添加元素到指定的位置

    public void add(int index, E element) {

if (index > size || index <0)

throw new IndexOutOfBoundsException(

"Index: " + index +", Size: " + size);

        // 修改modCount

        ensureCapacity(size +1);  // Increments modCount!!

        System.arraycopy(elementData, index, elementData, index +1,

                size - index);

        elementData[index] = element;

        size++;

    }

// 添加集合

    public boolean addAll(Collection c) {

Object[] a = c.toArray();

        int numNew = a.length;

        // 修改modCount

        ensureCapacity(size + numNew);  // Increments modCount

        System.arraycopy(a, 0, elementData, size, numNew);

        size += numNew;

        return numNew !=0;

    }

// 删除指定位置的元素

    public E remove(int index) {

RangeCheck(index);

        // 修改modCount

        modCount++;

        E oldValue = (E) elementData[index];

        int numMoved = size - index -1;

        if (numMoved >0)

System.arraycopy(elementData, index +1, elementData, index, numMoved);

        elementData[--size] =null; // Let gc do its work

        return oldValue;

    }

// 快速删除指定位置的元素

    private void fastRemove(int index) {

// 修改modCount

        modCount++;

        int numMoved = size - index -1;

        if (numMoved >0)

System.arraycopy(elementData, index +1, elementData, index,

                    numMoved);

        elementData[--size] =null; // Let gc do its work

    }

// 清空集合

    public void clear() {

// 修改modCount

        modCount++;

        // Let gc do its work

        for (int i =0; i < size; i++)

elementData[i] =null;

        size =0;

    }

...

}

通过代码我们可以看到:只要更改集合中的元素个数:例如add(),remove(),clear()都会改变ModCount的值。

接下来,我们再系统的梳理一下fail-fast是怎么产生的。步骤如下:

(01) 新建了一个ArrayList,名称为arrayList。

(02) 向arrayList中添加内容。

(03)新建一个“线程a”,并在“线程a”中通过Iterator反复的读取arrayList的值

(04) 新建一个“线程b”,在“线程b”中删除arrayList中的一个“节点A”。

(05) 之后,就会产生fail-fast事件了,即:“线程a”创建了arrayList的Iterator。此时“节点A”仍然存在于arrayList中,创建arrayList时,expectedModCount = modCount(假设它们此时的值为N)。

       在“线程a”在遍历arrayList过程中的某一时刻,“线程b”执行了,并且“线程b”删除了arrayList中的“节点A”。“线程b”执行remove()进行删除操作时,在remove()中执行了“modCount++”,此时modCount变成了N+1

“线程a”接着遍历,当它执行到next()函数时,调用checkForComodification()比较“expectedModCount”和“modCount”的大小;而“expectedModCount=N”,“modCount=N+1”,这样,便抛出ConcurrentModificationException异常,产生fail-fast事件。

如何避免fail-fast问题的产生呢?java.util.current包帮你解决。看一下CopyOnWriteArrayList

public class CopyOnWriteArrayList

implements List, RandomAccess, Cloneable, java.io.Serializable {

...

// 返回集合对应的迭代器

    public Iteratoriterator() {

return new COWIterator(getArray(), 0);

    }

...

private static class COWIteratorimplements ListIterator {

private final Object[]snapshot;

        private int cursor;

        private COWIterator(Object[] elements, int initialCursor) {

cursor = initialCursor;

            // 新建COWIterator时,将集合中的元素保存到一个新的拷贝数组中。

            // 这样,当原始集合的数据改变,拷贝数据中的值也不会变化。

            snapshot = elements;

        }

public boolean hasNext() {

return cursor

        }

public boolean hasPrevious() {

return cursor >0;

        }

public E next() {

if (!hasNext())

throw new NoSuchElementException();

            return (E)snapshot[cursor++];

        }

public E previous() {

if (!hasPrevious())

throw new NoSuchElementException();

            return (E)snapshot[--cursor];

        }

public int nextIndex() {

return cursor;

        }

public int previousIndex() {

return cursor -1;

        }

public void remove() {

throw new UnsupportedOperationException();

        }

public void set(E e) {

throw new UnsupportedOperationException();

        }

public void add(E e) {

throw new UnsupportedOperationException();

        }

}

...

}

(01) 和ArrayList继承于AbstractList不同,CopyOnWriteArrayList没有继承于AbstractList,它仅仅只是实现了List接口。

(02) ArrayList的iterator()函数返回的Iterator是在AbstractList中实现的;而CopyOnWriteArrayList是自己实现Iterator。

(03) ArrayList的Iterator实现类中调用next()时,会“调用checkForComodification()比较‘expectedModCount’和‘modCount’的大小”;但是,CopyOnWriteArrayList的Iterator实现类中,没有所谓的checkForComodification(),更不会抛出ConcurrentModificationException异常。



个人公号:【排骨肉段】,可以关注一下。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容