搜索
房产
装修
汽车
婚嫁
健康
理财
旅游
美食
跳蚤
二手房
租房
招聘
二手车
教育
茶座
我要买房
买东西
装修家居
交友
职场
生活
网购
亲子
情感
龙城车友
找美食
谈婚论嫁
美女
兴趣
八卦
宠物
手机

JAVA中fail-fast机制

[复制链接]
查看: 95|回复: 0

1万

主题

1万

帖子

4万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
45261
发表于 2020-2-14 18:52 | 显示全部楼层 |阅读模式
在JDK的Collection中我们经常会看到类似于这样的话:
例如,ArrayList:
留意,迭代器的快速失利活动没法获得保证,由于一样平常来说,不大要对能否出现不同步并发点窜做出任何硬性保证。快速失利迭代器会尽最大积极抛出 ConcurrentModificationException。是以,为进步这类迭代器的正确性而编写一个依靠于此很是的步伐是毛病的做法:迭代器的快速失利活动应当仅用于检测 bug。
HashMap中:
留意,迭代器的快速失利活动不能获得保证,一样平常来说,存在非同步的并发点窜时,不大要作出任何果断的保证。快速失利迭代器尽最大积极抛出 ConcurrentModificationException。是以,编写依靠于此很是的步伐的做法是毛病的,正确做法是:迭代器的快速失利活动应当仅用于检测步伐毛病。
在这两段话中频频地提到”快速失利”。那末作甚”快速失利”机制呢?
“快速失利”也就是fail-fast,它是Java聚集的一种毛病检测机制。当多个线程对聚集举行结构上的改变的操纵时,有大要会发生fail-fast机制。记着是有大要,而不是必定。例如:假定存在两个线程(线程1、线程2),线程1经过Iterator在遍历聚集A中的元素,在某个时候线程2点窜了聚集A的结构(是结构上面的点窜,而不是简单的点窜聚集元素的内容),那末这个时候步伐就会抛出 ConcurrentModificationException 很是,从而发生fail-fast机制。
一、fail-fast示例
  1. public class FailFastTest {    private static List list = new ArrayList();        /**     * @desc:线程one迭代list     * @Project:test     * @file:FailFastTest.java     * @Authro:chenssy     * @data:2014年7月26日     */    private static class threadOne extends Thread{        public void run() {            Iterator iterator = list.iterator();            while(iterator.hasNext()){                int i = iterator.next();                System.out.println("ThreadOne 遍历:" + i);                try {                    Thread.sleep(10);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }        /**     * @desc:当i == 3时,点窜list     * @Project:test     * @file:FailFastTest.java     * @Authro:chenssy     * @data:2014年7月26日     */    private static class threadTwo extends Thread{        public void run(){            int i = 0 ;             while(i < 6){                System.out.println("ThreadTwo run:" + i);                if(i == 3){                    list.remove(i);                }                i++;            }        }    }        public static void main(String[] args) {        for(int i = 0 ; i < 10;i++){            list.add(i);        }        new threadOne().start();        new threadTwo().start();    }}
复制代码
运转结果:
  1. ThreadOne 遍历:0ThreadTwo run:0ThreadTwo run:1ThreadTwo run:2ThreadTwo run:3ThreadTwo run:4ThreadTwo run:5Exception in thread "Thread-0" java.util.ConcurrentModificationException    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)    at java.util.ArrayList$Itr.next(Unknown Source)    at test.ArrayListTest$threadOne.run(ArrayListTest.java:23)
复制代码
二、fail-fast发生原因起因

经过上面的示例息争说,我初步晓得fail-fast发生的原因起因就在于步伐在对 collection 举行迭代时,某个线程对该 collection 在结构上对其做了点窜,这时迭代器就会抛出 ConcurrentModificationException 很是信息,从而发生 fail-fast。
要了解fail-fast机制,我们起首要对ConcurrentModificationException 很是有所了解。当方式检测到工具的并发点窜,但不答应这类点窜时就抛出该很是。同时必要留意的是,该很是不会始终指出工具已经过不同线程并发点窜,假如单线程违反了法则,一样也有大要会抛出改很是。
固然,迭代器的快速失利活动没法获得保证,它不能保证必定会出现该毛病,可是快速失利操纵会尽最大积极抛出ConcurrentModificationException很是,所以是以,为进步此类操纵的正确性而编写一个依靠于此很是的步伐是毛病的做法,正确做法是:ConcurrentModificationException 应当仅用于检测 bug。下面我将以ArrayList为例进一步分析fail-fast发生的原因起因。
畴前面我们晓得fail-fast是在操纵迭代器时发生的。现在我们来看看ArrayList中迭代器的源代码:
  1. private class Itr implements Iterator {        int cursor;        int lastRet = -1;        int expectedModCount = ArrayList.this.modCount;         public boolean hasNext() {            return (this.cursor != ArrayList.this.size);        }         public E next() {            checkForComodification();            /** 省略此处代码 */        }         public void remove() {            if (this.lastRet < 0)                throw new IllegalStateException();            checkForComodification();            /** 省略此处代码 */        }         final void checkForComodification() {            if (ArrayList.this.modCount == this.expectedModCount)                return;            throw new ConcurrentModificationException();        }    }
复制代码
从上面的源代码我们可以看出,迭代器在挪用next()、remove()方式时都是挪用checkForComodification()方式,该方式严重就是检测modCount == expectedModCount ? 若不等则抛出ConcurrentModificationException 很是,从而发生fail-fast机制。所以要弄清楚为什么会发生fail-fast机制我们就必必要用弄明白为什么modCount != expectedModCount ,他们的值在什么时候发生改变的。
   expectedModCount 是在Itr中界说的:int expectedModCount = ArrayList.this.modCount;所以他的值是不大要会点窜的,所以会变的就是modCount。modCount是在 AbstractList 中界说的,为全局变量:
  1. protected transient int modCount = 0;
复制代码
那末他什么时候由于什么原因起因而发生改变呢?请看ArrayList的源码:
  1.     public boolean add(E paramE) {        ensureCapacityInternal(this.size + 1);        /** 省略此处代码 */    }     private void ensureCapacityInternal(int paramInt) {        if (this.elementData == EMPTY_ELEMENTDATA)            paramInt = Math.max(10, paramInt);        ensureExplicitCapacity(paramInt);    }        private void ensureExplicitCapacity(int paramInt) {        this.modCount += 1;    //点窜modCount        /** 省略此处代码 */    }       public boolean remove(Object paramObject) {        int i;        if (paramObject == null)            for (i = 0; i < this.size; ++i) {                if (this.elementData[i] != null)                    continue;                fastRemove(i);                return true;            }        else            for (i = 0; i < this.size; ++i) {                if (!(paramObject.equals(this.elementData[i])))                    continue;                fastRemove(i);                return true;            }        return false;    }     private void fastRemove(int paramInt) {        this.modCount += 1;   //点窜modCount        /** 省略此处代码 */    }     public void clear() {        this.modCount += 1;    //点窜modCount        /** 省略此处代码 */    }
复制代码
从上面的源代码我们可以看出,ArrayList中不管add、remove、clear方式只如果触及了改变ArrayList元素的个数的方式城市致使modCount的改变。所以我们这里可以初步判定由于expectedModCount 得值与modCount的改变不同步,致使两者之间不等从而发生fail-fast机制。晓得发生fail-fast发生的底子原因起因了,我们可以有如结局景:
有两个线程(线程A,线程B),其中线程A负责遍历list、线程B点窜list。线程A在遍历list进程的某个时候(此时expectedModCount = modCount=N),线程启动,同时线程B增加一个元素,这是modCount的值发生改变(modCount + 1 = N + 1)。线程A继续遍历尝试next方式时,通告checkForComodification方式发现expectedModCount = N ,而modCount = N + 1,两者不等,这时就抛出ConcurrentModificationException 很是,从而发生fail-fast机制。
所以,直到这里我们已经完全了解了fail-fast发生的底子原因起因了。晓得了原因起因就好找治理法子了。
三、fail-fast治理法子

经过前面的实例、源码分析,我想列位已经根抵细识了fail-fast的机制,下面我就发生的原因起因提出治理计划。这里有三种治理计划:
计划一:在对arraylist点窜modcount的操纵时加锁,同时在全部迭代器操纵时对arraylist工具加锁,这样保证在迭代器尝试时代,modcount没法被改变。可是不举荐,由于增删酿成的同步锁大要会阻塞遍历操纵。
计划二:尽管操纵部分变量,这样底子上治理线程平安题目,同时在留意下同一线程时迭代器进程中不要对list做点窜modcount操纵
计划三:操纵CopyOnWriteArrayList来更换ArrayList。举荐操纵该计划。
CopyOnWriteArrayList为何物?ArrayList 的一个线程平安的变体,其中全数可变操纵(add、set 等等)都是经过对底层数组举行一次新的复制来实现的。 该类发生的开销比力大,可是在两种情况下,它很是适当操纵。1:在不能或不想举行同步遍历,但又必要从并发线程中断根辩说时。2:当遍历操纵的数目大大超出可变操纵的数目时。碰到这两种情况操纵CopyOnWriteArrayList来更换ArrayList再适当不外了。那末为什么CopyOnWriterArrayList可以更换ArrayList呢?
第一、CopyOnWriterArrayList的不管是从数据结构、界说都和ArrayList一样。它和ArrayList一样,一样是实现List接口,底层操纵数组实现。在方式上也包含add、remove、clear、iterator等方式。
第二、CopyOnWriterArrayList底子就不会发生ConcurrentModificationException很是,也就是它操纵迭代器完全不会发生fail-fast机制。请看:
  1. private static class COWIterator implements ListIterator {        /** 省略此处代码 */        public E next() {            if (!(hasNext()))                throw new NoSuchElementException();            return this.snapshot[(this.cursor++)];        }         /** 省略此处代码 */    }
复制代码
CopyOnWriterArrayList的方式底子就没有像ArrayList中操纵checkForComodification方式来判定expectedModCount 与 modCount 能否相当。它为什么会这么做,凭什么可以这么做呢?我们以add方式为例:
  1. public boolean add(E paramE) {        ReentrantLock localReentrantLock = this.lock;        localReentrantLock.lock();        try {            Object[] arrayOfObject1 = getArray();            int i = arrayOfObject1.length;            Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1);            arrayOfObject2[i] = paramE;            setArray(arrayOfObject2);            int j = 1;            return j;        } finally {            localReentrantLock.unlock();        }    }         final void setArrayObject[] paramArrayOfObject) {        this.array = paramArrayOfObject;    }
复制代码
CopyOnWriterArrayList的add方式与ArrayList的add方式有一个最大的不同点就在于,下面三句代码:
  1. Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1);arrayOfObject2[i] = paramE;setArray(arrayOfObject2);
复制代码
就是这三句代码使得CopyOnWriterArrayList不会抛ConcurrentModificationException很是。他们所显现的魅力就在于copy本来的array,再在copy数组上举行add操纵,这样做就完全不会影响COWIterator中的array了。
所以CopyOnWriterArrayList所代表的焦点概念就是:任何对array在结构上有所改变的操纵(add、remove、clear等),CopyOnWriterArrayList城市copy现有的数据,再在copy的数据上点窜,这样就不会影响COWIterator中的数据了,点窜完成以后改变原稀有据的援用即可。同时这样酿成的价格就是发生大量的工具,同时数组的copy也是相当有消耗的。

站在伟人的肩膀上摘苹果:

原文链接:https://blog.csdn.net/chenssy/article/details/38151189

免责声明:假如加害了您的权益,请联系站长,我们会实时删除侵权内容,感谢合作!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Copyright © 2006-2014 全椒百姓网-全椒知名**,发布及时新鲜的全椒新闻资讯 生活信息 版权所有 法律顾问:高律师 客服电话:0791-88289918
技术支持:迪恩网络科技公司  Powered by Discuz! X3.2
快速回复 返回顶部 返回列表