Java 集合深入理解 :java.util 包的集合中 快速失败机制( fail-fast )

Posted 踩踩踩从踩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 集合深入理解 :java.util 包的集合中 快速失败机制( fail-fast )相关的知识,希望对你有一定的参考价值。

前言
快速失败机制是java集合中用来保证遍历过程中数据安全的机制 总的来说就是对比expectedModCount = modCount 判断数据操作,它并不判断数据是否有冲突的操作,而是看遍历时操作是否正确,依此达到数据安全。

for循环遍历并删除数据,但不抛出异常

我们可以区别一下下面的 通过for循环去删除数据这只是会导致数据删除不正确,原因是:会导致2和4未删除,list动态的变化了,遍历时也会随之变化

	public static void main(String[] args) {
		List<String> list=new ArrayList<String>();
		list.add("1");
		list.add("2");
		list.add("3");
		list.add("4");
		list.add("5");
		for(int i=0;i<list.size();i++) {
			list.remove(i);
		}
		list.stream().forEach(i->{
			System.out.println(i);
		});
	}
	
	2
    4

单线程抛出异常

在迭代器中边遍历边删除,会抛出ConcurrentModificationException异常

 List<String> list = new ArrayList<>();
		  list.add("1");
		  list.add("2");
		  list.add("3");
		  list.add("4");
		  list.add("5");
          Iterator<String> iterator = list.iterator();
          while(iterator.hasNext()) {
        	  System.out.println(iterator.next());
        	   list.remove(3);
          }
          1
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at org.cao.test.Area.main(Area.java:40)

多线程中抛出异常

public static void main(String[] args) {
			List<String> list=new ArrayList<String>();
			list.add("1");
			list.add("2");
			list.add("3");
			list.add("4");
			list.add("5");
			new Thread() {
				@Override
				public void run() {
					 Iterator<String> iterator = list.iterator();
				                while(iterator.hasNext()) {
				                	try {
				                     String s = iterator.next();
				                     System.out.println( "thread1:" + s);
				                     
				                    Thread.sleep(1000);
				                } catch (Exception e) {
				                	System.out.println( "thread1 e:" + e.getMessage());
				                    e.printStackTrace();
				                    return;
				                }
				               };
					super.run();
				}
			}.start();
			new Thread() {
				@Override
				public void run() {
						for(int i=0;i<list.size();i++) {
							  System.out.println("thread2:" + i);
							   list.remove(i);
							   
							   try {
								Thread.sleep(1000);
							} catch (InterruptedException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
						}
					 

					super.run();
				}
			}.start();
	}
thread1:1
thread2:0
thread2:1
thread1 e:null
java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at org.cao.test.Area$1.run(Area.java:47)
thread2:2

fail-fast 机制是什么

而fail-fast 机制是Java集合(Collection)中的一种错误机制。当多个线程(单线程也可能)对同一个集合的内容进行操作时,就可能会产生fail-fast(快速失败)事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

主要实现是来自通过 modCount 域保证数据的安全性,modCount 是用来记录此列表在结构上被修改的次数。结构修改是指改变结构尺寸的修改列表,或者以这样的方式对其进行扰动的

modCount 是什么

 /**
*此列表在结构上被修改的次数。结构修改是指改变结构尺寸的修改列表,或者以这样的方式对其进行扰动
*进步可能会产生错误的结果。
*<p>此字段由迭代器和列表迭代器实现使用
*由{@CodeIterator}和{@CodeListIterator}方法返回。
*如果此字段的值意外更改,则迭代器(或列表迭代器)将在
*对{@code next}、{@code remove}、{@code previous}的响应,
*{@code set}或{@code add}操作。这提供了<i>快速失败</i>行为,而不是迭代过程中并发修改的情况。
*<p><b>子类使用此字段是可选的。</b>如果子类希望提供故障快速迭代器(和列表迭代器),那么只需在{@code add(int,E)}和
*{@code remove(int)}方法(以及它覆盖的任何其他方法)
*这将导致对列表进行结构性修改)。一通电话
*{@code add(int,E)}或{@code remove(int)}添加的内容不能超过
*一个到这个字段,或者迭代器(和列表迭代器)将抛出
*伪代码{@code ConcurrentModificationExceptions}。如果一个实现
*不希望提供失败快速迭代器,此字段可能是已忽略。
*/
    protected transient int modCount = 0;

就是记录此列表在结构上被修改的次数。结构修改是指改变结构尺寸的修改列表,就是add remove等方法

arrayList中的迭代器

 /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            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];
        }

        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();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

迭代器初始化时
将这个值赋给迭代器的 expectedModCount;
并且初始化 cursor,主要用于记录刚刚遍历过的元素的索引
lastRet 最后一个元素的索引 删除则会设置为-1

   int cursor;       // 要返回的下一个元素的索引
   int lastRet = -1; // 返回的最后一个元素的索引-1如果没有
   int expectedModCount = modCount;

hasnext方法
判断是否是最后一个数据,当数据迭代器迭代结束hasNext()返回会false


        public boolean hasNext() {
            return cursor != size;
        }

next方法
在next方法中为了保证数据安全性
会用checkForComodification 方法 去校验 expectedModCount值和modCount是否一致。如果不一致则会抛出异常。
在遍历中 他会先检测if (i >= size) 检测i是否大约size抛异常。
并且判断 cursor 是否大于elementData.length 如果大于等于则 抛ConcurrentModificationException

 @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];
        }
         final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

迭代器中remove方法

会将expectedModCount = modCount; 会重新赋值

   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();
            }
        }

在arraylist中对Itr做了一个扩展,包括ListItr 包括add set方法
在java提供的集合中在遍历过程中,使用快速失败来保证数据的安全。
并不保证保证多线程中的数据安全性,jdk在集合注释上都会建议 我们使用 Collections.synchronizedList 或者 java.util.concurrent 包下面的CopyOnWriteArrayList 或者ConcurrentHashMap等来保证多线程下数据安全性

以上是关于Java 集合深入理解 :java.util 包的集合中 快速失败机制( fail-fast )的主要内容,如果未能解决你的问题,请参考以下文章

Java源码分析:深入探讨Iterator模式

并发编程7:深入理解Java虚拟机-锁优化

文章标题

java基础之集合&数组

java.util包详解——Connection接口

聊聊高并发(二十四)解析java.util.concurrent各个组件 深入理解AQS