Java中的fail-fast机制使用详解
Java的fail-fast机制使用
fail-fast 机制是Java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
例如:
- 当某一个线程 A 通过 iterator 去遍历某集合的过程中,若该集合的内容被其他线程所改变了,那么线程 A 访问集合时,就会抛出 ConcurrentModificationException 异常,产生 fail-fast 事件。
- 这里的操作主要是指 add、remove 和 clear,对集合元素个数进行修改。
举例代码
单线程,在foreach循环里对某些集合元素进行元素的remove/add操作的时候,会触发fail-fast机制
public static void main(String[] args){
List<String> strList = new ArrayList<>();
strList.add("AA");
strList.add("aa");
strList.add("BB");
strList.add("CC");
for(String str : strList){
if("aa".equals(str)){
strList.remove(str);
}
}
}多线程,在一个线程读时,另一个线程写入list,读线程会fail-fast
// 测试
public class TreadDemo1 {
public static void main(String[] args){
List<String> strList = new ArrayList<>();
strList.add("AA");
strList.add("aa");
strList.add("BB");
strList.add("CC");
strList.add("DD");
new MyThread1(strList).start();
new MyThread2(strList).start();
}
static class Mythread1 extends Thread {
private List<String> list;
public Mythread1(List<String> list){
this.list = list;
}
@Override
public void run(){
for(String str : list){
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("MtThread1:"+str);
}
}
}
static class MyThread2 extends Thread {
private list<String> list;
public Mythread2(List<String> list){
this.list = list;
}
@Override
public void run(){
for(int i = 0; i < list.size(); i++){
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
if("aa".equals(list.get(i))){
list.remove(i);
}
}
System.out.println("MtThread2:"+list);
}
}
}原理
将单线程编译后的.class反编译后发现,foreach其实是依赖while循环和Iterator实现的。
通过跟踪代码的异常堆栈,发现真正抛出异常的代码是:java.util.ArrayList$Itr.checkForComodification();该方法实在iterator.next()方法中调用的:
final void checkForComodification(){
if(modCount != expectedModCount)
throw new ConcurrentModificationException();
}在该方法中modCount 和 expectedModCount进行了比较,如果二者不相等,则抛出ConcurrentModificationException 异常。
modCount是ArrayList中的一个成员变量。表示该集合实际被修改的次数。(操作集合类的remove()、add()、clear()方法会改变这个变量值)expectedModCount是ArrayList 中的一个内部类---Itr(Iterator接口)中的成员变量。表示这个迭代器预期该集合被修改的次数。其值随着Itr被创建而初始化。只有通过迭代器对集合进行操作,该值才会改变。
所以,在使用Java的集合类的时候,如果发生ConcurrentModificationException 异常,优先考虑fail-fast有关的情况。
解决方式
1)使用普通for循环进行操作
普通for循环没有使用到Iterator的遍历,所以不会进行fail-fast的检验。
public static void main(String[] args){
List<String> strList = new ArrayList<>();
strList.add("AA");
strList.add("aa");
strList.add("BB");
strList.add("CC");
strList.add("DD");
for(int i = 0; i < strList.size(); i++){
if("aa".equals(strList.get(i))){
strList.remove(i);
}
}
}2)直接使用Iterator 进行操作
public static void main(String[] args){
List<String> strList = new ArrayList<>();
strList.add("AA");
strList.add("aa");
strList.add("BB");
strList.add("CC");
strList.add("DD");
Iterator<String> iterator = strList.iterator();
while(iterator.hasNext()){
if("aa".equals(iterator.next())){
iterator.remove();
}
}
}3)使用Java 8中提供的filter 过滤
Java 8 中可以把集合转换成流,对于流有一种filter操作,可以对原始Stream 进行某项过滤,通过过滤的元素被留下了生成一个新的Stream。
public static void main(String[] args){
List<String> strList = new ArrayList<>();
strList.add("AA");
strList.add("aa");
strList.add("BB");
strList.add("CC");
strList.add("DD");
strList = strList.stream().filter(e -> !"aa".equals(e)).collect(Collectors.toList());
System.out.println(strList);
}4)使用fail-safe的集合类
为了避免触发fail-fast机制导致异常,我们可以使用Java中提供的一些采用了fail-safe机制的集合类。
java.util.concurrent包下的容器都是fail-safe的,可以在多线程下并发使用,并发修改。同时也可以在foreach中进行add/remove等操作。
5)也可以使用foreach循环
如果我们非常确定一个集合中,某个即将删除的元素只包含一个的话,也是可以使用foreach循环的,只要删除之后,立即结束循环体,不在继续执行遍历就可以。
public static void main(String[] args){
List<String> strList = new ArrayList<>();
strList.add("AA");
strList.add("aa");
strList.add("BB");
strList.add("CC");
for(String str : strList){
if("aa".equals(str)){
strList.remove(str);
break;
}
}
System.out.println(strList);
}总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
相关文章
Spring Security SecurityContextHolder组件示例说明
SpringSecurity的SecurityContextHolder组件是存储当前安全上下文的地方,包括认证用户信息,它支持全局访问、线程局部存储和上下文传播,是SpringSecurity认证和授权的核心,文章通过示例展示了如何访问已认证用户的详细信息、手动设置认证信息以及使用认证信息保护方法2024-11-11
SpringBoot AnnotationUtils工具类的使用实例详解
这篇文章主要介绍了SpringBoot AnnotationUtils工具类的使用,使用自定义注解标记业务方法,原生Java获取注解及AnnotationUtils工具类获取方法,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下2022-09-09
Java字符编码转换(从UTF-8到GBK)的实现原理与实践
在计算机处理文本的过程中,字符编码转换是跨语言、跨平台数据交互的核心环节,本文详细介绍了Java中Unicode与GBK字符集之间的映射原理及UTF-8与GBK编码之间的转换逻辑,帮助开发者理解底层机制并避免编码问题,需要的朋友可以参考下2026-01-01
MybatisX-Generator不生成domain文件夹的问题及解决
在使用MybatisX-Generator生成数据库表实体时,如果发现没有生成domain文件夹以及User.java文件,是因为MybatisX版本更新,最新版需要在options里额外勾选model才能生成domain,勾选model并点击finish后,成功生成domain文件夹及User.java文件2025-01-01


最新评论