简介

fail-fast 机制,即快速失败机制,是java集合(Collection)中的一种错误检测机制

  • 当在迭代集合的过程中该集合在结构上发生改变的时候,就有可能会发生fail-fast,即抛出ConcurrentModificationException异常
  • fail-fast机制并不保证在不同步的修改下一定会抛出异常,它只是尽最大努力去抛出,所以这种机制一般仅用于检测bug


案例

出现场景

迭代器每次next()/hasNext()时都会判断modCount!=expectedmodCount

  • modCount:列表在结构上被修改的次数
  • expectedmodCount:预期列表结构被修改次数
  • 如果二者不相等就抛出ConcurrentModificationException异常
  • 这两个在遍历前是相等的,如果循环中执行了add,remove,clear操作都会修改modCount,set操作不会修改modCount

image.png 使用foreach语法糖时,不能add,remove,clear也是一样的道理,foreach底层就是iterator

  1. public static void main(String[] args) {
  2. List<String> list = new ArrayList<>();
  3. list.addAll(Arrays.asList("A","B","C"));
  4. System.out.println("list = " + list);
  5. Iterator<String> it = list.iterator();
  6. while (it.hasNext()){
  7. String elem = it.next();
  8. list.remove(elem);
  9. // it.remove();
  10. }
  11. System.out.println("list = " + list);
  12. }

解决方案

iterator迭代器自带的remove

遍历过程中使用迭代器的remove方法,删除当前元素(每次remove后会重新给expectedModCount赋值)
image.png

CopyOnWriteArrayList

  1. public static void main(String[] args) {
  2. List<String> list = new CopyOnWriteArrayList<>();
  3. list.addAll(Arrays.asList("A","B","C"));
  4. System.out.println("list = " + list);
  5. Iterator<String> it = list.iterator();
  6. while (it.hasNext()){
  7. list.remove(it.next());
  8. }
  9. System.out.println("list = " + list);
  10. }

CopyOnWriteArrayList的删除操作

  1. 加锁
  2. 找到需删除元素的索引位置
  3. 新建一个比原容器容量少1的新容器
  4. 复制原容器删除位置之前的全部元素到新容器中
  5. 复制原容器删除位置之后的全部元素到新容器中
  6. 原容器的引用指向新容器,丢弃原容器
  7. 解锁
  1. //不加锁,只是用来查询一些必要信息
  2. public boolean remove(Object o) {
  3. Object[] snapshot = getArray();
  4. int index = indexOf(o, snapshot, 0, snapshot.length);
  5. return (index < 0) ? false : remove(o, snapshot, index);
  6. }
  7. // o:要删除的元素; snapshot:列表快照; index:o在列表中的索引位置
  8. private boolean remove(Object o, Object[] snapshot, int index) {
  9. // 加锁
  10. final ReentrantLock lock = this.lock;
  11. lock.lock();
  12. try {
  13. Object[] current = getArray();
  14. int len = current.length;
  15. // remove(Object o)并发执行了,这里进行检查,查到要删除元素真正的索引
  16. if (snapshot != current) findIndex: {
  17. int prefix = Math.min(index, len);
  18. // 容器在index索引前面的结构发生了变化
  19. for (int i = 0; i < prefix; i++) {
  20. // prefix前有索引位置元素发生变化,如果该位置元素变成我们需要的,
  21. // 直接返回这个位置
  22. if (current[i] != snapshot[i] && eq(o, current[i])) {
  23. index = i;
  24. break findIndex;
  25. }
  26. }
  27. // 当前容器长度<=该元素索引位置,说明当前容器不存在该元素
  28. if (index >= len)
  29. return false;
  30. // index位置元素是我们需要的,找到需要的元素索引
  31. if (current[index] == o)
  32. break findIndex;
  33. // 遍历当前容器,查找我们要删除元素的索引
  34. index = indexOf(o, current, index, len);
  35. if (index < 0)
  36. return false;
  37. }
  38. // 新建一个比原容器容量少1的新容器
  39. Object[] newElements = new Object[len - 1];
  40. // 复制原容器删除位置之前的全部元素到新容器中
  41. System.arraycopy(current, 0, newElements, 0, index);
  42. // 复制原容器删除位置之后的全部元素到新容器中
  43. System.arraycopy(current, index + 1,
  44. newElements, index,
  45. len - index - 1);
  46. // 原容器的引用指向新容器
  47. setArray(newElements);
  48. return true;
  49. } finally {
  50. // 解锁
  51. lock.unlock();
  52. }
  53. }
  54. // 原容器的引用指向新容器
  55. final void setArray(Object[] a) {
  56. array = a;
  57. }