HashMap和Hashtable的fail-fast机制

如果需要在迭代过程中修改删除HashMap或者Hashtable中的数据,需要使用Iterator迭代器,Hashtable中也可以使用Enumeraction来迭代。

HashMap正常迭代代码:

 @Test
    public void testHashMapIterator() throws Exception {
        Map<String,String> hashMap = new HashMap<String,String>();
        hashMap.put("key1","value1");
        hashMap.put("key2","value2");
        hashMap.put("key3","value3");
        hashMap.put("key4","value4");
        
        Iterator<Entry<String, String>> it = hashMap.entrySet().iterator();
        while(it.hasNext()) {
            System.out.println(it.next());
            it.remove();
            //hashMap.remove("key1");
        }
    }

会抛出ConcurrentModificationException的修改方式(不限于下面这种,使用forEach语句也是会报错的,forEach底层依然是简单的for循环)

//java.util.ConcurrentModificationException
    @Test
    public void testHashMapIteratorException() throws Exception {
        Map<String,String> hashMap = new HashMap<String,String>();
        hashMap.put("key1","value1");
        hashMap.put("key2","value2");
        hashMap.put("key3","value3");
        hashMap.put("key4","value4");
        
        Iterator<Entry<String, String>> it = hashMap.entrySet().iterator();
        while(it.hasNext()) {
            System.out.println(it.next());
            hashMap.remove("key1");
        }
    }

Hashtable的用Iterator正常迭代删除数据和错误迭代删除数据和HashMap都是一样的,下面说一下Hashtable中Enumeraction实现迭代的方式,这种方式在迭代的时候修改数据也是不会报异常的。
    

    @Test
    public void testHashtableEnumeration() throws Exception {
        Hashtable<String,String> hashTable = new Hashtable<String,String>();
        hashTable.put("key1","value1");
        hashTable.put("key2","value2");
        hashTable.put("key3","value3");
        hashTable.put("key4","value4");
        
        Enumeration<String> e = hashTable.elements();
        while(e.hasMoreElements()) {
            hashTable.remove("key1");
            System.out.println(e.nextElement());
        }
    }

HashIteractor底层代码

    abstract class HashIterator {
        Node<K,V> next;        // next entry to return
        Node<K,V> current;     // current entry
        int expectedModCount;  // for fast-fail
        int index;             // current slot

        HashIterator() {
            expectedModCount = modCount;
            ...//省略部分代码
        }

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
        ...//省略部分代码
    }

对于modCount官方注释是The number of times this HashMap has been structurally modified,也就是这个变量是用来记录HashMap结构修改的次数

所以可以猜测当用Iterator实例的remove去修改集合中的元素,modCount的值是不会变化的,而直接用集合的remove方法修改集合中的元素modCount++,为什么这么做的原因目前还没理解,静待后面的学习。

去查了一下

参考:

https://blog.youkuaiyun.com/tony521125/article/details/83965226

<think>我们正在讨论Java集合框架中的fail-fastfail-safe机制。根据引用内容,我们可以总结如下: 1. **fail-fast(快速失败)机制**: - 在遍历集合时,如果集合结构被修改(如添加、删除元素),则会立即抛出`ConcurrentModificationException`异常。 - 这种机制出现在非线程安全的集合类中,如`ArrayList`、`HashMap`等(除了并发集合)。 - 原理:迭代器在遍历过程中会维护一个修改计数器(modCount),每次对集合的修改都会增加这个计数器。在迭代过程中,迭代器会检查modCount是否与预期一致,如果不一致就抛出异常。 2. **fail-safe(安全失败)机制**: - 在遍历集合时,允许在遍历过程中修改集合,不会抛出异常。 - 实现方式:通常是通过创建集合的一个副本(快照)来遍历,或者使用并发安全的迭代器。 - 这种机制出现在并发集合类中,如`CopyOnWriteArrayList`、`ConcurrentHashMap`等。 现在,我们根据引用内容来详细解释: ### 1. fail-fast机制 根据引用[1][2][3]: - fail-fast是Java集合的一种错误检测机制,当多个线程对同一个集合进行操作时,如果其中一个线程在遍历集合,而另一个线程修改了集合结构(非同步修改),那么迭代器会立即抛出`ConcurrentModificationException`。 - 注意:即使单线程环境下,如果在迭代过程中直接调用集合的`add`、`remove`等方法(而不是通过迭代器的`remove`方法)修改集合,也会触发fail-fast机制。 示例代码(触发fail-fast): ```java List<String> list = new ArrayList<>(); list.add("A"); list.add("B"); Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); if (s.equals("B")) { list.remove(s); // 这里会抛出ConcurrentModificationException } } ``` ### 2. fail-safe机制 根据引用[5]: - fail-safe机制允许在遍历的同时修改集合,不会抛出异常。这是因为迭代器操作的是集合的一个快照(备份),而不是原始集合。 - 典型代表:`java.util.concurrent`包下的并发集合类,如`CopyOnWriteArrayList`、`ConcurrentHashMap`。 示例代码(fail-safe): ```java CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); list.add("A"); list.add("B"); list.add("C"); Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); if (s.equals("B")) { list.remove(s); // 不会抛出异常 } } ``` ### 3. 关于HashTable的讨论 根据引用[4]: - HashTable使用Enumeration遍历时,在遍历过程中删除元素不会抛出异常(即fail-safe行为),但使用Iterator遍历时删除元素会抛出ConcurrentModificationException(fail-fast行为)。 - 注意:HashTable本身是线程安全的,但它的迭代器并不是fail-safe的(除了Enumeration)。实际上,HashTable的Iterator是fail-fast的,而Enumeration不是快速失败的。 ### 总结对比 | **特性** | **fail-fast** | **fail-safe** | |------------------|--------------------------------------------------|----------------------------------------------------| | **异常抛出** | 检测到并发修改时立即抛出`ConcurrentModificationException` | 不会抛出异常 | | **实现原理** | 通过modCount检查修改次数 | 遍历集合的副本(快照)或使用并发安全迭代器 | | **性能** | 较高(无复制开销) | 较低(需要复制集合,内存开销大) | | **集合示例** | `ArrayList`, `HashMap`, `HashSet` | `CopyOnWriteArrayList`, `ConcurrentHashMap` | | **线程安全** | 非线程安全(需要外部同步) | 线程安全 | ### 注意事项 - **单线程环境**:使用fail-fast集合时,应避免在迭代过程中直接修改集合(使用迭代器的`remove`方法除外)。 - **多线程环境**:如果需要并发修改遍历,应使用fail-safe的并发集合类。 ### 相关问题 1. 为什么在迭代`ArrayList`时删除元素会抛出`ConcurrentModificationException`?如何避免? 2. `CopyOnWriteArrayList`是如何实现fail-safe机制的?它的优缺点是什么? 3. 在Java中,`ConcurrentHashMap`的迭代器是fail-fast还是fail-safe?为什么? 4. 如何理解`ConcurrentModificationException`?它只在多线程环境下发生吗?</think>### Java集合框架中的Fail-FastFail-Safe机制详解 #### 1. **Fail-Fast(快速失败)机制** ⚡️ **核心特征**: - 在遍历集合时,**一旦检测到结构修改**(添加/删除元素),立即抛出`ConcurrentModificationException` - 通过**修改计数器**(`modCount`)实现:迭代器创建时记录初始值,每次操作前校验是否变化 - 设计目标:**快速暴露并发问题**,防止数据不一致 **实现原理**: ```java final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } ``` *(源码来自ArrayList.Itr类)* **典型集合**: - `ArrayList`, `HashMap`, `HashSet`等非线程安全集合 - `HashTable`的`Iterator`实现(但`Enumeration`不是)[^4] **触发场景**: ```java List<String> list = new ArrayList<>(List.of("A", "B")); Iterator<String> it = list.iterator(); while(it.hasNext()) { System.out.println(it.next()); list.add("C"); // 抛出ConcurrentModificationException } ``` --- #### 2. **Fail-Safe(安全失败)机制** 🛡️ **核心特征**: - 允许**遍历与修改同时进行**,不会抛出异常 - 实现方式:**遍历集合的快照副本** 或 **弱一致性迭代器** - 设计目标:**保证高并发场景的稳定性** **实现原理**: - `CopyOnWriteArrayList`:遍历时操作原始数组的不可变副本 - `ConcurrentHashMap`:弱一致性迭代器,可能反映部分修改 **典型集合**: - `java.util.concurrent`包中的并发集合 - `CopyOnWriteArrayList`, `ConcurrentHashMap`, `ConcurrentSkipListSet` **安全修改示例**: ```java CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); list.addAll(List.of("A", "B", "C")); Iterator<String> it = list.iterator(); while(it.hasNext()) { System.out.println(it.next()); // 输出A,B,C list.add("D"); // ✅ 不会中断迭代 } System.out.println(list); // 输出[A, B, C, D, D, D] ``` *(基于引用[5]的案例优化)* --- ### 核心对比表 | **特性** | **Fail-Fast** | **Fail-Safe** | |------------------------|-----------------------------------|-----------------------------------| | **异常行为** | 立即抛出`ConcurrentModificationException` | 不抛出异常 | | **数据一致性** | 强一致性 | 弱一致性(可能不反映最新修改) | | **实现方式** | 修改计数器校验 | 集合快照/弱一致性迭代器 | | **内存开销** | 低 | 较高(需维护副本) | | **典型集合** | `ArrayList`, `HashMap` | `CopyOnWriteArrayList`, `ConcurrentHashMap` | | **适用场景** | 单线程环境 | 高并发多线程环境 | --- ### 关键知识点 1. **HashTable的特殊性**: - 使用`Iterator`时表现Fail-Fast特性 - 使用`Enumeration`时表现Fail-Safe特性[^4] ```java Hashtable<String, String> table = new Hashtable<>(); Enumeration<String> en = table.keys(); while(en.hasMoreElements()) { System.out.println(en.nextElement()); table.put("newKey", "value"); // ✅ 不报错(Fail-Safe行为) } ``` 2. **Fail-Safe的代价**: - **内存消耗**:`CopyOnWriteArrayList`每次修改都复制整个数组 - **数据延迟**:迭代器可能无法立即看到最新修改 --- ### 相关问题 1. 为什么`CopyOnWriteArrayList`的迭代器不支持`remove()`操作? 2. 如何在单线程环境下避免触发Fail-Fast机制? 3. `ConcurrentHashMap`的弱一致性迭代器具体表现是什么? 4. Java中哪些集合既不是Fail-Fast也不是Fail-Safe? 5. Fail-Fast机制是否绝对能检测到所有并发修改?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值