@@ -255,57 +255,51 @@ public interface RandomAccess {
255
255
256
256
详见笔主的这篇文章: [ ArrayList 扩容机制分析] ( https://javaguide.cn/java/collection/arraylist-source-code.html#_3-1-%E5%85%88%E4%BB%8E-arraylist-%E7%9A%84%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E8%AF%B4%E8%B5%B7 ) 。
257
257
258
- ### 说说集合中的fail-fast和fail-safe是什么
258
+ ### 说说集合中的 fail-fast 和 fail-safe 是什么
259
259
260
260
关于` fail-fast ` 引用` medium ` 中一篇文章关于` fail-fast ` 和` fail-safe ` 的说法:
261
261
262
262
> 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.
263
263
264
-
265
264
快速失败的思想即针对可能发生的异常进行提前表明故障并停止运行,通过尽早的发现和停止错误,降低故障系统级联的风险。
266
265
267
266
在` java.util ` 包下的大部分集合是不支持线程安全的,为了能够提前发现并发操作导致线程安全风险,提出通过维护一个` modCount ` 记录修改的次数,迭代期间通过比对预期修改次数` expectedModCount ` 和` modCount ` 是否一致来判断是否存在并发操作,从而实现快速失败,由此保证在避免在异常时执行非必要的复杂代码。
268
267
269
268
对应的我们给出下面这样一段在示例,我们首先插入` 100 ` 个操作元素,一个线程迭代元素,一个线程删除元素,最终输出结果如愿抛出` ConcurrentModificationException ` :
270
269
271
270
``` java
272
- ArrayList<Integer > list = new ArrayList<> ();
273
- CountDownLatch countDownLatch = new CountDownLatch (2 );
274
- // 添加几个元素
275
- for (int i = 0 ; i < 100 ; i++ ) {
276
- list. add(i);
277
- }
271
+ // 使用线程安全的 CopyOnWriteArrayList 避免 ConcurrentModificationException
272
+ List<Integer > list = new CopyOnWriteArrayList<> ();
273
+ CountDownLatch countDownLatch = new CountDownLatch (2 );
278
274
279
- Thread t1 = new Thread (() - > {
280
- // 迭代元素
281
- for (Integer i : list) {
282
- i++ ;
283
- }
284
- countDownLatch. countDown();
285
- });
275
+ // 添加元素
276
+ for (int i = 0 ; i < 100 ; i++ ) {
277
+ list. add(i);
278
+ }
286
279
280
+ Thread t1 = new Thread (() - > {
281
+ // 迭代元素 (注意:Integer 是不可变的,这里的 i++ 不会修改 list 中的值)
282
+ for (Integer i : list) {
283
+ i++ ; // 这行代码实际上没有修改list中的元素
284
+ }
285
+ countDownLatch. countDown();
286
+ });
287
287
288
- Thread t2 = new Thread (() - > {
289
- System . out. println(" 删除元素1" );
290
- list. remove(1 );
291
- countDownLatch. countDown();
292
- });
288
+ Thread t2 = new Thread (() - > {
289
+ System . out. println(" 删除元素1" );
290
+ list. remove(Integer . valueOf( 1 )); // 使用 Integer.valueOf(1) 删除指定值的对象
291
+ countDownLatch. countDown();
292
+ });
293
293
294
- t1. start();
295
- t2. start();
296
- countDownLatch. await();
294
+ t1. start();
295
+ t2. start();
296
+ countDownLatch. await();
297
297
```
298
298
299
+ 我们在初始化时插入了` 100 ` 个元素,此时对应的修改` modCount ` 次数为` 100 ` ,随后线程 2 在线程 1 迭代期间进行元素删除操作,此时对应的` modCount ` 就变为` 101 ` 。
300
+ 线程 1 在随后` foreach ` 第 2 轮循环发现` modCount ` 为` 101 ` ,与预期的` expectedModCount(值为100因为初始化插入了元素100个) ` 不等,判定为并发操作异常,于是便快速失败,抛出` ConcurrentModificationException ` :
299
301
300
-
301
-
302
-
303
-
304
- 我们在初始化时插入了` 100 ` 个元素,此时对应的修改` modCount ` 次数为` 100 ` ,随后线程2在线程1迭代期间进行元素删除操作,此时对应的` modCount ` 就变为` 101 ` 。
305
- 线程1在随后` foreach ` 第2轮循环发现` modCount ` 为` 101 ` ,与预期的` expectedModCount(值为100因为初始化插入了元素100个) ` 不等,判定为并发操作异常,于是便快速失败,抛出` ConcurrentModificationException ` :
306
-
307
- ![ ] ( https://qiniuyun.sharkchili.com/202411172341886.png )
308
-
302
+ ![ ] ( https://oss.javaguide.cn/github/javaguide/java/collection/fail-fast-and-fail-safe-insert-100-values.png )
309
303
310
304
对此我们也给出` for ` 循环底层迭代器获取下一个元素时的` next ` 方法,可以看到其内部的` checkForComodification ` 具有针对修改次数比对的逻辑:
311
305
@@ -326,16 +320,13 @@ final void checkForComodification() {
326
320
327
321
```
328
322
329
-
330
323
而` fail-safe ` 也就是安全失败的含义,它旨在即使面对意外情况也能恢复并继续运行,这使得它特别适用于不确定或者不稳定的环境:
331
324
332
-
333
325
> 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.
334
326
335
327
该思想常运用于并发容器,最经典的实现就是` CopyOnWriteArrayList ` 的实现,通过写时复制的思想保证在进行修改操作时复制出一份快照,基于这份快照完成添加或者删除操作后,将` CopyOnWriteArrayList ` 底层的数组引用指向这个新的数组空间,由此避免迭代时被并发修改所干扰所导致并发操作安全问题,当然这种做法也存缺点即进行遍历操作时无法获得实时结果:
336
- ![ ] ( https://qiniuyun.sharkchili.com/202411172352477.png )
337
-
338
328
329
+ ![ ] ( https://oss.javaguide.cn/github/javaguide/java/collection/fail-fast-and-fail-safe-copyonwritearraylist.png )
339
330
340
331
对应我们也给出` CopyOnWriteArrayList ` 实现` fail-safe ` 的核心代码,可以看到它的实现就是通过` getArray ` 获取数组引用然后通过` Arrays.copyOf ` 得到一个数组的快照,基于这个快照完成添加操作后,修改底层` array ` 变量指向的引用地址由此完成写时复制:
341
332
0 commit comments