@@ -257,49 +257,75 @@ public interface RandomAccess {
257257
258258### ⭐️集合中的 fail-fast 和 fail-safe 是什么?
259259
260+ ` fail-fast ` (快速失败)和 ` fail-safe ` (安全失败)是Java集合框架在处理并发修改问题时,两种截然不同的设计哲学和容错策略。
261+
260262关于` fail-fast ` 引用` medium ` 中一篇文章关于` fail-fast ` 和` fail-safe ` 的说法:
261263
262264> Fail-fast systems are designed to immediately stop functioning upon encountering an unexpected condition. This immediate failure helps to catch errors early, making debugging more straightforward.
263265
264266快速失败的思想即针对可能发生的异常进行提前表明故障并停止运行,通过尽早的发现和停止错误,降低故障系统级联的风险。
265267
266- 在` java.util ` 包下的大部分集合是不支持线程安全的 ,为了能够提前发现并发操作导致线程安全风险,提出通过维护一个` modCount ` 记录修改的次数,迭代期间通过比对预期修改次数` expectedModCount ` 和` modCount ` 是否一致来判断是否存在并发操作,从而实现快速失败,由此保证在避免在异常时执行非必要的复杂代码。
268+ 在` java.util ` 包下的大部分集合(如 ` ArrayList ` , ` HashMap ` )是不支持线程安全的 ,为了能够提前发现并发操作导致线程安全风险,提出通过维护一个` modCount ` 记录修改的次数,迭代期间通过比对预期修改次数` expectedModCount ` 和` modCount ` 是否一致来判断是否存在并发操作,从而实现快速失败,由此保证在避免在异常时执行非必要的复杂代码。
267269
268- 对应的我们给出下面这样一段在示例,我们首先插入 ` 100 ` 个操作元素,一个线程迭代元素,一个线程删除元素,最终输出结果如愿抛出 ` ConcurrentModificationException ` :
270+ ** ArrayList (fail-fast) 示例: **
269271
270272``` java
271- // 使用线程安全的 CopyOnWriteArrayList 避免 ConcurrentModificationException
272- List<Integer > list = new CopyOnWriteArrayList<> ();
273- CountDownLatch countDownLatch = new CountDownLatch (2 );
274-
275- // 添加元素
276- for (int i = 0 ; i < 100 ; i++ ) {
277- list. add(i);
278- }
279-
280- Thread t1 = new Thread (() - > {
281- // 迭代元素 (注意:Integer 是不可变的,这里的 i++ 不会修改 list 中的值)
282- for (Integer i : list) {
283- i++ ; // 这行代码实际上没有修改list中的元素
284- }
285- countDownLatch. countDown();
286- });
273+ // 使用线程不安全的 ArrayList,它是一种 fail-fast 集合
274+ List<Integer > list = new ArrayList<> ();
275+ CountDownLatch latch = new CountDownLatch (2 );
276+
277+ for (int i = 0 ; i < 5 ; i++ ) {
278+ list. add(i);
279+ }
280+ System . out. println(" Initial list: " + list);
281+
282+ Thread t1 = new Thread (() - > {
283+ try {
284+ for (Integer i : list) {
285+ System . out. println(" Iterator Thread (t1) sees: " + i);
286+ Thread . sleep(100 );
287+ }
288+ } catch (ConcurrentModificationException e) {
289+ System . err. println(" !!! Iterator Thread (t1) caught ConcurrentModificationException as expected." );
290+ } catch (InterruptedException e) {
291+ e. printStackTrace();
292+ } finally {
293+ latch. countDown();
294+ }
295+ });
296+
297+ Thread t2 = new Thread (() - > {
298+ try {
299+ Thread . sleep(50 );
300+ System . out. println(" -> Modifier Thread (t2) is removing element 1..." );
301+ list. remove(Integer . valueOf(1 ));
302+ System . out. println(" -> Modifier Thread (t2) finished removal." );
303+ } catch (InterruptedException e) {
304+ e. printStackTrace();
305+ } finally {
306+ latch. countDown();
307+ }
308+ });
309+
310+ t1. start();
311+ t2. start();
312+ latch. await();
313+
314+ System . out. println(" Final list state: " + list);
315+ ```
287316
288- Thread t2 = new Thread (() - > {
289- System . out. println(" 删除元素1" );
290- list. remove(Integer . valueOf(1 )); // 使用 Integer.valueOf(1) 删除指定值的对象
291- countDownLatch. countDown();
292- });
317+ 输出:
293318
294- t1. start();
295- t2. start();
296- countDownLatch. await();
319+ ```
320+ Initial list: [0, 1, 2, 3, 4]
321+ Iterator Thread (t1) sees: 0
322+ -> Modifier Thread (t2) is removing element 1...
323+ -> Modifier Thread (t2) finished removal.
324+ !!! Iterator Thread (t1) caught ConcurrentModificationException as expected.
325+ Final list state: [0, 2, 3, 4]
297326```
298327
299- 我们在初始化时插入了` 100 ` 个元素,此时对应的修改` modCount ` 次数为` 100 ` ,随后线程 2 在线程 1 迭代期间进行元素删除操作,此时对应的` modCount ` 就变为` 101 ` 。
300- 线程 1 在随后` foreach ` 第 2 轮循环发现` modCount ` 为` 101 ` ,与预期的` expectedModCount(值为100因为初始化插入了元素100个) ` 不等,判定为并发操作异常,于是便快速失败,抛出` ConcurrentModificationException ` :
301-
302- ![ ] ( https://oss.javaguide.cn/github/javaguide/java/collection/fail-fast-and-fail-safe-insert-100-values.png )
328+ 程序在线程 t2 修改列表后,线程 t1 的下一次迭代操作立刻就抛出了 ` ConcurrentModificationException ` 。这是因为 ArrayList 的迭代器在每次 ` next() ` 调用时,都会检查 ` modCount ` 是否被改变。一旦发现集合在迭代器不知情的情况下被修改,它会立即“快速失败”,以防止在不一致的数据上继续操作导致不可预期的后果。
303329
304330对此我们也给出` for ` 循环底层迭代器获取下一个元素时的` next ` 方法,可以看到其内部的` checkForComodification ` 具有针对修改次数比对的逻辑:
305331
@@ -324,7 +350,7 @@ final void checkForComodification() {
324350
325351> Fail-safe systems take a different approach, aiming to recover and continue even in the face of unexpected conditions. This makes them particularly suited for uncertain or volatile environments.
326352
327- 该思想常运用于并发容器,最经典的实现就是` CopyOnWriteArrayList ` 的实现,通过写时复制的思想保证在进行修改操作时复制出一份快照 ,基于这份快照完成添加或者删除操作后,将` CopyOnWriteArrayList ` 底层的数组引用指向这个新的数组空间,由此避免迭代时被并发修改所干扰所导致并发操作安全问题,当然这种做法也存在缺点,即进行遍历操作时无法获得实时结果:
353+ 该思想常运用于并发容器,最经典的实现就是` CopyOnWriteArrayList ` 的实现,通过写时复制(Copy-On-Write)的思想保证在进行修改操作时复制出一份快照 ,基于这份快照完成添加或者删除操作后,将` CopyOnWriteArrayList ` 底层的数组引用指向这个新的数组空间,由此避免迭代时被并发修改所干扰所导致并发操作安全问题,当然这种做法也存在缺点,即进行遍历操作时无法获得实时结果:
328354
329355![ ] ( https://oss.javaguide.cn/github/javaguide/java/collection/fail-fast-and-fail-safe-copyonwritearraylist.png )
330356
0 commit comments