(简单易懂)Java的快速失败(fail-fast)与安全失败,源码分析+详细讲解
之前在程序中遇到快速失败的问题,在网上找解释时发现网上的问题总结都比较片面,故打算自己总结一个,也可以供以后参考。
--宇的季节
首先什么是快速失败?
快速失败是为了提示程序员在多线程的情况下不要用线程不安全的集合(bug)的一种机制。
当然在单线程的情况下有时也会出现ConcurrentModificationException异常,下面我们就根据ArrayList来探索快速失败的内部实现方法。
示例一:
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
//为集合添加100个数
for (int i = 0; i < 100; i++)
list.add(i+"");
//获取集合的迭代器
Iterator<String> iter = list.iterator();
//我们在集合中删除一个数据,注意我们是直接用集合操作而不是迭代器
list.remove("2");
//再使用迭代器获取数据
iter.next();
}
执行结果:
果不其然,报了ConcurrentModificationException的错误。
为什么会报错呢?下面我们就来看看ArrayList的源码(为了方便阅读,把没用的都省略了)
public class ArrayList<E> ,,,,, { .... //modCount:集合的修改次数 protected transient int modCount = 0; //当出现增删操作时,便会modCount++; public E remove(int index) { .... modCount++; ...... } .... //通过此方法获取迭代器 public Iterator<E> iterator() { return new Itr(); } .... //迭代器是ArrayList的一个内部类 private class Itr implements Iterator<E> { ... //每次获取一个迭代器,就新建一个对象,将expectedModCount赋值为modCount int expectedModCount = modCount; ..... @SuppressWarnings("unchecked") public E next() { //检查expectedModCount和modCount是否相等 checkForComodification(); ..... } //这个就是检查的方法 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } ..... }
我们已经了解了ArrayList的大概结构,那么示例一的报错也就不难解释了:
假设创建一个迭代器时modCount=10,那么expectedModCount=10(注意,modCount是ArrayList的参数,而expectedModCount是迭代器的参数,从创建迭代器之后,他们就没联系了)
当执行list.remove("2");操作时,modCount++=11;而此时expectedModCount=10;当我们再执行iter.next()时,很明显两个数不一致,抛出异常。
结论:当我们在获取一个快速失败集合(只要不是concurrent包下的集合都是快速失败的)的迭代器时候,之后在操作迭代器的中间不能对集合本身再进行增删操作,否则会抛异常。
但是,快速失败机制的意义并不在此,上面已经说到了,快速失败是为了警告程序员不能在多线程情况下使用线程不安全的集合。
示例2:
public class test {
private static List<String> list = new ArrayList<String>();
public static void main(String[] args) {
for (int i = 0; i < 100; i++)
list.add(i+"");
System.out.println("线程开始了");
new threadOne().start();
new threadTwo().start();
}
//线程1每隔10ms遍历一次
private static class threadOne extends Thread{
public void run(){
Iterator<String> iter = list.iterator();
while(iter.hasNext()){
System.out.println("线程1遍历到了:"+iter.next());
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//线程1每隔10ms删除一个
private static class threadTwo extends Thread{
public void run(){
Iterator<String> iter = list.iterator();
while(iter.hasNext()){
System.out.println("线程2删除了:"+iter.next());
iter.remove();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
执行结果:
在示例2中,我们并没有在使用迭代器中间直接对集合进行增删。但是依然报错。
原因就是因为线程的异步性!
这次只看迭代器的remove()操作即可:
可以看到,两个值的改变被分成了两条程序语句,那么就会出现这种情况:
线程1删除时执行到语句1,modCount+1。
由于语句2还没执行,expectedModCount不会改变
这时处理器跳转到线程2执行。
线程2执行next()时,对比modCount和expectedModCount。
发现两值不一致,抛出ConcurrentModificationException
终于把快速失败搞明白了!那么什么是安全失败呢?
这个就简单了,在使用安全失败的迭代器时,会copy一个集合,之后的操作都是对这个copy的“副本”进行操作。
那么就算是多线程,操作的也是创建迭代器时copy的单独的副本。
所以不会造成冲突,ConcurrentModificationException也就不复存在啦~
(简单易懂)Java的快速失败(fail-fast)与安全失败,源码分析+详细讲解的更多相关文章
- Java显式锁学习总结之六:Condition源码分析
概述 先来回顾一下java中的等待/通知机制 我们有时会遇到这样的场景:线程A执行到某个点的时候,因为某个条件condition不满足,需要线程A暂停:等到线程B修改了条件condition,使con ...
- Java显式锁学习总结之五:ReentrantReadWriteLock源码分析
概述 我们在介绍AbstractQueuedSynchronizer的时候介绍过,AQS支持独占式同步状态获取/释放.共享式同步状态获取/释放两种模式,对应的典型应用分别是ReentrantLock和 ...
- Java并发包中Semaphore的工作原理、源码分析及使用示例
1. 信号量Semaphore的介绍 我们以一个停车场运作为例来说明信号量的作用.假设停车场只有三个车位,一开始三个车位都是空的.这时如果同时来了三辆车,看门人允许其中它们进入进入,然后放下车拦.以后 ...
- Java显式锁学习总结之四:ReentrantLock源码分析
概述 ReentrantLock,即重入锁,是一个和synchronized关键字等价的,支持线程重入的互斥锁.只是在synchronized已有功能基础上添加了一些扩展功能. 除了支持可中断获取锁. ...
- 转!!Java学习之自动装箱和自动拆箱源码分析
自动装箱(boxing)和自动拆箱(unboxing) 首先了解下Java的四类八种基本数据类型 基本类型 占用空间(Byte) 表示范围 包装器类型 boolean 1/8 true|fal ...
- Java学习之自动装箱和自动拆箱源码分析
自动装箱(boxing)和自动拆箱(unboxing) 首先了解下Java的四类八种基本数据类型 基本类型 占用空间(Byte) 表示范围 包装器类型 boolean 1/8 true|false ...
- 【Java】CAS的乐观锁实现之AtomicInteger源码分析
1. 悲观锁与乐观锁 我们都知道,cpu是时分复用的,也就是把cpu的时间片,分配给不同的thread/process轮流执行,时间片与时间片之间,需要进行cpu切换,也就是会发生进程的切换.切换涉及 ...
- Java - "JUC线程池" 线程状态与拒绝策略源码分析
Java多线程系列--“JUC线程池”04之 线程池原理(三) 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基本概念"中,我们介绍过,线程有5种状态:新建 ...
- Java 自动装箱、拆箱机制及部分源码分析
Integer i = 10; //装箱,反编译后发现调用Integer.valueOf(int i) int t = i; //拆箱,反编译后发现调用i.intValue() public clas ...
随机推荐
- css动画属性--轮播图效果
通过css的动画属性实现轮播图的显示效果 代码如下: 主体部分: <div id="move"> <ul> <li><img src=&q ...
- 华为OJ之自动售货系统
本题主要难点有两部分: 1,找零算法.如何找零应该是最具技巧性的部分,根据已有的硬币金额分布,对应的解决办法可能会有不同.本题中的1,2,5,10这种情况满足贪心性质,故我们简单的用贪心算法的思想来解 ...
- c++ vector(向量)使用方法详解(顺序访问vector的多种方式)
来源:http://www.jb51.net/article/44231.htm 作者: 字体:[增加 减小] 类型:转载 时间:2013-12-08我要评论 vector是向量类型,它可以容纳许多类 ...
- 工具 | 代码调试利器fiddle介绍
我们开发的系统运行在用户的环境上,为了保护我们的代码和提升性能,前端javascript是经过压缩的.压缩的代码难于定位,当前只有chrome对压缩的代码支持格式化,但是变量和函数简化后,定位依然困难 ...
- android调用系统相机进行视频录制并保存到指定目录
最近在做视频录制上传,调用的是系统的相机. 在做之前查了一些资料,发现好多人遇到保存到指定目录不成功的现象.自己写的时候就注意这些,最后发现他们遇到的问题我这边根本没有.可能是他们写法有问题吧. 下边 ...
- C#设计模式(1)-单例模式
单例(Singleton)模式介绍 单例模式:也可以叫单件模式,官方定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 单例模式的特点: 单例类只能有一个实例. 单例类必须自己创建自己的唯一 ...
- 染色[SDOI2011]
题目描述 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如"11 ...
- git分支管理之Bug分支
软件开发中,bug就像家常便饭一样.有了bug就需要修复,在Git中,由于分支是如此的强大,所以,每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除. 当你接到一个修复一 ...
- NYOJ--122--Triangular Sums
Triangular Sums 时间限制:3000 ms | 内存限制:65535 KB 难度:2 描述 The nth Triangular number, T(n) = 1 + - + n ...
- Autofac in webapi2
安装包:Autofac.webapi2 注意: install-package autofac.webapi2 (注意:您的项目中如果使用的是webapi2,此处必须为webapi2而不是webapi ...