Java 集合系列04之 fail-fast总结

转载自   Java 集合系列04之 fail-fast总结

概要

前面,我们已经学习了ArrayList。接下来,我们以ArrayList为例,对Iterator的fail-fast机制进行了解。内容包括::
1 fail-fast简介
2 fail-fast示例
3 fail-fast解决办法
4 fail-fast原理
5 解决fail-fast的原理

1 fail-fast简介

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

在详细介绍fail-fast机制的原理之前,先通过一个示例来认识fail-fast。

  

2 fail-fast示例

示例代码:(FastFailTest.java)

import java.util.*;
import java.util.concurrent.*;/** @desc java集合中Fast-Fail的测试程序。**   fast-fail事件产生的条件:当多个线程对Collection进行操作时,若其中某一个线程通过iterator去遍历集合时,该集合的内容被其他线程所改变;则会抛出ConcurrentModificationException异常。*   fast-fail解决办法:通过util.concurrent集合包下的相应类去处理,则不会产生fast-fail事件。**   本例中,分别测试ArrayList和CopyOnWriteArrayList这两种情况。ArrayList会产生fast-fail事件,而CopyOnWriteArrayList不会产生fast-fail事件。*   (01) 使用ArrayList时,会产生fast-fail事件,抛出ConcurrentModificationException异常;定义如下:*            private static List<String> list = new ArrayList<String>();*   (02) 使用时CopyOnWriteArrayList,不会产生fast-fail事件;定义如下:*            private static List<String> list = new CopyOnWriteArrayList<String>();** @author skywang*/
public class FastFailTest {private static List<String> list = new ArrayList<String>();//private static List<String> list = new CopyOnWriteArrayList<String>();public static void main(String[] args) {// 同时启动两个线程对list进行操作!new ThreadOne().start();new ThreadTwo().start();}private static void printAll() {System.out.println("");String value = null;Iterator iter = list.iterator();while(iter.hasNext()) {value = (String)iter.next();System.out.print(value+", ");}}/*** 向list中依次添加0,1,2,3,4,5,每添加一个数之后,就通过printAll()遍历整个list*/private static class ThreadOne extends Thread {public void run() {int i = 0;while (i<6) {list.add(String.valueOf(i));printAll();i++;}}}/*** 向list中依次添加10,11,12,13,14,15,每添加一个数之后,就通过printAll()遍历整个list*/private static class ThreadTwo extends Thread {public void run() {int i = 10;while (i<16) {list.add(String.valueOf(i));printAll();i++;}}}}

运行结果
运行该代码,抛出异常java.util.ConcurrentModificationException!即,产生fail-fast事件!

结果说明
(01) FastFailTest中通过 new ThreadOne().start() 和 new ThreadTwo().start() 同时启动两个线程去操作list。
    ThreadOne线程:向list中依次添加0,1,2,3,4,5。每添加一个数之后,就通过printAll()遍历整个list。
    ThreadTwo线程:向list中依次添加10,11,12,13,14,15。每添加一个数之后,就通过printAll()遍历整个list。
(02) 当某一个线程遍历list的过程中,list的内容被另外一个线程所改变了;就会抛出ConcurrentModificationException异常,产生fail-fast事件。

 

3 fail-fast解决办法

fail-fast机制,是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。若在多线程环境下使用fail-fast机制的集合,建议使用“java.util.concurrent包下的类”去取代“java.util包下的类”。
所以,本例中只需要将ArrayList替换成java.util.concurrent包下对应的类即可。

即,将代码

private static List<String> list = new ArrayList<String>();

替换为

private static List<String> list = new CopyOnWriteArrayList<String>();

则可以解决该办法。

 

4 fail-fast原理

产生fail-fast事件,是通过抛出ConcurrentModificationException异常来触发的。
那么,ArrayList是如何抛出ConcurrentModificationException异常的呢?

我们知道,ConcurrentModificationException是在操作Iterator时抛出的异常。我们先看看Iterator的源码。ArrayList的Iterator是在父类AbstractList.java中实现的。代码如下: 

package java.util;public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {...// AbstractList中唯一的属性// 用来记录List修改的次数:每修改一次(添加/删除等操作),将modCount+1protected transient int modCount = 0;// 返回List对应迭代器。实际上,是返回Itr对象。public Iterator<E> iterator() {return new Itr();}// Itr是Iterator(迭代器)的实现类private class Itr implements Iterator<E> {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)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事件。

要搞明白 fail-fast机制,我们就要需要理解什么时候“modCount 不等于 expectedModCount”!
从Itr类中,我们知道 expectedModCount 在创建Itr对象时,被赋值为 modCount。通过Itr,我们知道:expectedModCount不可能被修改为不等于 modCount。所以,需要考证的就是modCount何时会被修改。

接下来,我们查看ArrayList的源码,来看看modCount是如何被修改的。

package java.util;public class ArrayList<E> extends AbstractList<E>implements List<E>, 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) {// 修改modCountensureCapacity(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);// 修改modCountensureCapacity(size+1);  // Increments modCount!!System.arraycopy(elementData, index, elementData, index + 1,size - index);elementData[index] = element;size++;}// 添加集合public boolean addAll(Collection<? extends E> c) {Object[] a = c.toArray();int numNew = a.length;// 修改modCountensureCapacity(size + numNew);  // Increments modCountSystem.arraycopy(a, 0, elementData, size, numNew);size += numNew;return numNew != 0;}// 删除指定位置的元素 public E remove(int index) {RangeCheck(index);// 修改modCountmodCount++;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 workreturn oldValue;}// 快速删除指定位置的元素 private void fastRemove(int index) {// 修改modCountmodCount++;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() {// 修改modCountmodCount++;// Let gc do its workfor (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) 这时,就会产生有趣的事件了。
       在某一时刻,“线程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是如何产生的!
即,当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值);这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

 

5 解决fail-fast的原理

上面,说明了“解决fail-fast机制的办法”,也知道了“fail-fast产生的根本原因”。接下来,我们再进一步谈谈java.util.concurrent包中是如何解决fail-fast事件的。

还是以和ArrayList对应的CopyOnWriteArrayList进行说明。我们先看看CopyOnWriteArrayList的源码:

package java.util.concurrent;
import java.util.*;
import java.util.concurrent.locks.*;
import sun.misc.Unsafe;public class CopyOnWriteArrayList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable {...// 返回集合对应的迭代器public Iterator<E> iterator() {return new COWIterator<E>(getArray(), 0);}...private static class COWIterator<E> implements ListIterator<E> {private final Object[] snapshot;private int cursor;private COWIterator(Object[] elements, int initialCursor) {cursor = initialCursor;// 新建COWIterator时,将集合中的元素保存到一个新的拷贝数组中。// 这样,当原始集合的数据改变,拷贝数据中的值也不会变化。snapshot = elements;}public boolean hasNext() {return cursor < snapshot.length;}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异常!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/328471.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue.js说说组件

什么是组件&#xff1a;组件是Vue.js最强大的功能之一。组件可以扩展HTML元素&#xff0c;封装可重用的代码。在较高层面上&#xff0c;组件是自定义的元素&#xff0c;Vue.js的编译器为它添加特殊功能。在有些情况下&#xff0c;组件也可以是原生HTML元素的形式&#xff0c;以…

java 内存快照怎么看_jvm内存快照dump文件太大,怎么分析

1、场景通常&#xff0c;使用eclipse的mat图形化工具打开dump的时候都会内存溢出.对于比较小的dump&#xff0c;eclipse可以打开&#xff0c;但一旦dump文件太大&#xff0c;eclipse就有点束手无策。这时候怎么办呢&#xff1f;可以使用linux下的mat&#xff0c;既Memory Analy…

第3步 (请先看第2步再看第3步) 新建完spring+springmvc+mybatis项目 需要推送gitee仓库进行管理 巨详细

idea中Terminal终端无法执行GIT命令&#xff1a; touch README.md问题解决 解决方法二&#xff1a; ◆更改路径即可 Git\bin\bash.exe 或 Git\bin\sh.exe 然后重新启动idea即可 ********************88注意&#xff1a;每次更改完成后需要重新启动IDEA****************** 记…

115个Java面试题和答案——终极列表(下)

转载自 115个Java面试题和答案——终极列表&#xff08;下&#xff09;第一篇讨论了面向对象编程和它的特点&#xff0c;关于Java和它的功能的常见问题&#xff0c;Java的集合类&#xff0c;垃圾收集器&#xff0c;本章主要讨论异常处理&#xff0c;Java小应用程序&#xff0…

java编译提示错误信息_JAVA编译错误提示缺少“{”

展开全部有点粗心了 我把我修改过的发62616964757a686964616fe58685e5aeb931333335323435给你具体问题&#xff1a;是extends不是extands类Circle拼错Circle构造方法height拼错abstract class Shape{public int width,height;public Shape(int width,int height){this.widthwid…

谈谈这些年前端的变化

我从事web开发工作到现在差不多四年了&#xff0c;前后台都涉及过&#xff0c;近两年两年前端开发为主。我记得11年的时候&#xff0c;我到南京的第一年&#xff0c;加入了一个电商公司&#xff0c;从事PHP开发&#xff0c;那个时候公司里面有一个女孩子&#xff0c;专门从事前…

第2步 安装git 配置git用户 git的安装和项目的建立

安装git 比较简单 在window上是有个 git bash 这个命令集成了一部分window和linux的命令 在任意目录下(一般是项目工程目录下面) 右键点击git bash 进入之后 小写字母 i 进入编辑模式&#xff08;insert模式出入&#xff09; [user] nameyjb1091947832(注册…

jsp连接mysql数据库 例子_JSP 连接 MySQL 数据库的例子

一&#xff1a;数据库 1. 正确install mysql 2. mysql -h localhost -u root -p 3. create database shujuku; 4. grant all privileges on shujuku.* to testlocalhost identified by "12345"; 5. use shujuku; 6. create table biao (id int(8) primary key, name…

115个Java面试题和答案——终极列表(上)

转载自 115个Java面试题和答案——终极列表&#xff08;上&#xff09;本文我们将要讨论Java面试中的各种不同类型的面试题&#xff0c;它们可以让雇主测试应聘者的Java和通用的面向对象编程的能力。下面的章节分为上下两篇&#xff0c;第一篇将要讨论面向对象编程和它的特点&…

依赖注入的威力,.NET Core的魅力:解决MVC视图中的中文被html编码的问题

有园友在博问中提了这样一个问题 —— .NET Core 中文等非英文文字html编码输出问题&#xff0c;到我们的 ASP.NET Core 项目中一看&#xff0c;也是同样的问题。 比如下面的Razor视图代码&#xff1a; {ViewBag.Title "代码改变世界"; }<title>ViewBag.Title…

第4步 tomcat配置中文字符集 启动Tomcat  网页乱码

第二部&#xff1a;给页面上添加mate标签 <html> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"> <head> 然而并没什么效果。于是我就去掉这种无用的操作。换另外一种方案。 第三部&#xff1a;在tomcat的配置中…

自己动手开发编译器(我们的朋友 -- 装配脑袋走了)

编者: 昨天在微信群里得知 我们的朋友--装配脑袋(施凡老师),因白血病离我们而去,自从得病以来,施老师在积极的接受治疗&#xff0c;8.27 得到的消息还是比较让人开心的,大家都在期待着他能够重出江湖。装配脑袋一直在从事一个编译器项目,在博客园的博客里有个系列《自己动手开发…

Java Web开发与实战_Java Web开发技术与实战项目

【实例简介】北大青鸟ACCP6.0 使用JSP/Servlet/Ajax技术开发新闻发布系统第一部分课件以及代码答案1到6章【实例截图】【核心代码】7.《JavaWeb开发技术》└── 7.《JavaWeb开发技术》├── Chapter01│ ├── 01 提供给教员的内容│ │ ├── 01 教学演示案例│ │…

修改Eclipse中项目在Apache Tomcat中的部署路径

转载自 修改Eclipse中项目在Apache Tomcat中的部署路径在Eclipse中配置完服务器后&#xff0c;如果不做任何修改就去部署项目,部署后你会发现,在Apache Tomcat的webapp文件夹下并没有这个项目&#xff0c;那么项目去哪里了呢&#xff1f; 其实项目已经部署到如下默认目录…

微服务意味着分布式系统

Sander Hoogendoorn认为&#xff0c;向微服务迁移就意味着向分布式系统进行迁移&#xff0c;在这里&#xff0c;我们必须要处理延迟、认证与授权、无法到达的消息。通过使用微服务&#xff0c;我们能够将大型系统拆分为更小的组件&#xff0c;从而实现对架构的重新掌控。借助于…

在Eclipse中使用JUnit4进行单元测试(初级篇)

转载自 在Eclipse中使用JUnit4进行单元测试&#xff08;初级篇&#xff09;本文绝大部分内容引自这篇文章&#xff1a;http://www.devx.com/Java/Article/31983/0/page/1我们在编写大型程序的时候&#xff0c;需要写成千上万个方法或函数&#xff0c;这些函数的功能可能很强大…

java包 类 方法_Java中包与包之间方法的调用及其关键字区分(基础)

最近在写应用时&#xff0c;对包与包之间方法的调用不太清楚&#xff0c;专门看视频和网站后整理了这篇文章&#xff0c;希望对刚入门的同志有所帮助&#xff0c;进入主题。首先我们先简单看看Java中private、protected、public和default的主要区别&#xff1a;(看不懂没关系&a…

GitHub开源贡献榜:微软超越Facebook和Google排第一

在新任CEO萨蒂亚纳德拉的领导下&#xff0c;封闭许久的微软已于过去几年在变得更加拥抱开源。根据知名代码协作项目网站GitHub上的最新数据&#xff0c;我们发现微软已经位列开源贡献榜的第一名&#xff08;16419&#xff09;&#xff0c;超过了Facebook&#xff08;15682&…