广州网站建设公司嘉御,北京做机床的公司网站,中国做的网站国外能打开吗,盐城市滨海县建设局网站面试回答 在系统设计中#xff0c;快速失效#xff08;fail-fast#xff09;系统一种可以立即报告任何可能表明故障的情况的系统。快速失效系统通常设计用于停止正常操作#xff0c;而不是试图继续可能存在缺陷的过程。 其实#xff0c;这是一种理念#xff0c;说白了就是…面试回答 在系统设计中快速失效fail-fast系统一种可以立即报告任何可能表明故障的情况的系统。快速失效系统通常设计用于停止正常操作而不是试图继续可能存在缺陷的过程。 其实这是一种理念说白了就是在做系统设计的时候先考虑异常情况一旦发生异常直接停止并上报。
举一个最简单的 fail-fast 的例子 public int divide(int dividend,int divisor){if(divisor0){throw new RuntimeException(divisor cant be zero);}return dividend/divisor;
} 上面的代码是一个对两个整数做除法的方法在 divide 方法中我们对被除数做了个简单的检查如果其值为 0那么久直接抛出一个异常并明确提示异常原因。这其实就是 fail-fast 理念的实际应用。 这样做的好处就是可以预先识别出一些错误情况一方面可以避免执行复杂的其他代码另外一方面这种异常情况被识别之后也可以针对性的做一些单独处理。 在 Java 中集合类中有用到 fail-fast 机制进行设计一旦使用不当触发 fail-fast 机制设计的代码就会发生非预期情况。 在集合类中为了避免并发修改会维护一个 expectedModCount 属性他表示这个迭代器预期该集合被修改的次数。还有一个 modCount属性他表示该集合实际被修改的次数。在集合被修改时回去比较 modCount和 expectedModCount的值如果不一致则会触发 fail-fast 机制抛出 ConcurrentModificationException。 fail-safe 机制是为线程安全的集合准备的可以避免像 fail-fast 一样在并发使用集合的时候不断地抛出异常。 知识扩展
集合类中的 fail-fast
我们通常说的 Java 中的 fail-fast 机制默认指的是 Java 集合的一种错误检测机制。当多个线程对部分集合进行结构上的改变的操作时有可能会产生 fail-fast 机制这个时候就会抛出 ConcurrentModificationException。 ConcurrentModificationException当方法检测到对象的并发修改但不允许这种修改时就抛出该异常。 在 Java 中如果在 foreach 循环里对某些集合元素的 remove/add 操作的时候就会触发 fail-fast 机制进而抛出 ConcurrentModificationException。 如一下代码 public static void main(String[] args) {ListString userNamesnew ArrayListString(){{add(Tango);add(tango);add(TangoChi);add(C);}};for (String userName:userNames){if (userName.equals(Tango)){userNames.remove(userName);}}System.out.println(userNames);} 以上代码使用 for 循环遍历元素并尝试删除其中的 Tango 字符串元素。运行以上代码会抛出以下异常
Exception in thread main java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)at java.util.ArrayList$Itr.next(ArrayList.java:859)at com.chiyi.test.Main.main(Main.java:19) 同样的读者可以尝试在增强 for 循环中使用 add 方法添加元素结果也会同样抛出该异常。
在深入原理之前我们先尝试把 foreach 进行解语法糖看一下 foreach 具体如何实现的。
我们使用 jad 工具对编译的 class 进行反编译得到以下代码 public static void main(String args[]) {List userNames new ArrayList() {{add(Tango);add(tango);add(TangoChi);add(C);}};Iterator iterator userNames.iterator();do{if(!iterator.hasNext())break;String userName (String)iterator.next();if(userName.equals(Tango))userNames.remove(userName);} while(true);System.out.println(userNames);} 可以发现foreach 其实是依赖了 while 循环和 Iterator 实现的。 异常原理 通过以上代码异常堆栈我们可以跟踪到真正抛出异常的代码是 java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) 该方法是在 iterator.next()方法中调用的。我们看一下该方法的实现 final void checkForComodification() {if (modCount ! expectedModCount)throw new ConcurrentModificationException();
} 如上在该方法中对 modCount和 expectedModCount进行了比较如果二者不相等则抛出 ConcurrentModificationException。
那么modCount和 expectedModCount是什么是什么原因导致他们的值不相等的呢
modCount是 ArrayList 中的一个成员变量。它表示该集合实际被修改的次数。 List userNames new ArrayList() {{add(Tango);add(tango);add(TangoChi);add(C);}}; 当使用以上代码初始化集合之后该变量就有了。初始值为 0。
expectedModCount 是 ArrayList中的一个内部类 —— Itr 中的成员变量。 Iterator iterator userNames.iterator(); 以上代码即可得到一个 Itr 类该类实现了 Iterator接口。
expectedModCount 表示这个迭代器预期该集合被修改的次数。其值随着 Itr 被创建而初始化。只有通过迭代器对结合进行操作该值才会改变。
那么接着我们看下 userNames.remove(userName);方法里面做了什么事情为什么会导致 expectedModCount 和 modCount 的值不一样。
通过翻阅代码我们也可以发现remove方法的核心逻辑如下 private void fastRemove(int index) {modCount;int numMoved size - index - 1;if (numMoved 0)System.arraycopy(elementData, index1, elementData, index,numMoved);elementData[--size] null; // clear to let GC do its work
} 可以看到它只修改了 modCount并没有对 expectedModCount 做任何操作。 简单总结一下之所以会抛出 ConcurrentModificationException异常是因为我们的代码使用了增强 for 循环而在增强 for 循环中集合遍历是通过 iterator 进行的但是元素的 add/remove 却是直接使用的集合类自己的方法。这就导致 iterator 在遍历的时候会发现有一个元素在自己不知不觉的情况下就被删除/添加了就会抛出一个异常用来提示用户可能发生了并发修改
所以在使用 Java 的集合类的时候如果发生 ConcurrentModificationException优先考虑 fail-fast 有关的情况实际上这里并没有真的发生并发知识 iterator 使用了 fail-fast 的保护机制只要他发现有一次修改是未经自己进行的那么就会抛出异常。 fail-safe
为了避免触发 fail-fast 机制导致异常我们可以使用 Java 中提供的一些采用了 fail-safe 机制的集合类。
这样的集合容器在遍历时不是直接在集合内容上访问的而是先复制原有集合内容在拷贝的集合上进行遍历。
java.util.concurrent包下的容器都是 fail-safe 的可以在多线程下并发使用并发修改。同时也可以在 foreach 中进行 add/remove。
我们拿 CopyOnWriteArrayList这个 fail-safe 的集合类来简单分析一下。 public static void main(String[] args) {ListString userNamesnew CopyOnWriteArrayListString(){{add(Tango);add(tango);add(TangoChi);add(C);}};for (String userName:userNames){if (userName.equals(Tango)){userNames.remove(userName);}}System.out.println(userNames);} 以上代码使用 CopyOnWriteArrayList代替了 ArrayList就不会发生异常。
fail-safe 集合的所有对集合的修改都是先拷贝一份副本然后在副本集合上进行的并不是直接对原集合进行修改。并且这些修改方法如 add/remove都是通过加锁来控制并发的。
所以CopyOnWriteArrayList中的迭代器在迭代的过程中不需要做 fail-fast的并发检测。因为 fail-fast 的主要目的就是识别并发然后通过异常的方式通知用户
但是虽然基于拷贝内容的优点是避免了 ConcurrentModificationException但同样的迭代器并不能访问到修改后的内容。如以下代码 public static void main(String[] args) {ListString userNamesnew CopyOnWriteArrayListString(){{add(Tango);add(tango);add(TangoChi);add(C);}};Iterator ituserNames.listIterator();for (String userName:userNames){if (userName.equals(Tango)){userNames.remove(userName);}}System.out.println(userNames);while (it.hasNext()){System.out.println(it.next());}} 我们得到 CopyOnWriteArrayList 的 Iterator之后通过 for 循环直接删除原数组中的值最后在结尾处输出 Iterator结果发现内容如下 [tango, TangoChi, C]
Tango
tango
TangoChi
C 迭代器遍历的是开始遍历那一刻拿到的集合拷贝在遍历期间原集合发生的修改迭代器是不知道的。 什么是 Copy-On-Write 在了解了 CopyOnWriteArrayList之后不知道大家会不会有这样的疑问
他的 add/remove等方法都已经加锁了还要 copy 一份再修改干嘛多此一举同样是线程安全的集合这玩意和 Vector 有啥区别呢
Copy-On-Write 简称 COW是一种用于程序设计中的优化策略。其基本思路是从一开始大家都在共享同一个内容当某个人想要修改这个内容的时候才会真正把内容 Copy 出去形成一个新的内容然后再改这是一种延时懒惰策略。
CopyOnWrite 容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候不直接往当前容器添加而是先将当前容器进行 Copy复制出一个新的容器然后新的容器里添加元素添加完元素之后再将原容器的引用指向新的容器。
CopyOnWriteArrayList 中 add/remove等写方法是需要加锁的目的是为了避免 Copy 出 N 个副本出来导致并发写。
但是 CopyOnWriteArrayList 中的读方法是没有加锁的。 public E get(int index) {return get(getArray(), index);
} 这样做的好处是我们可以对 CopyOnWrite 容器进行并发的读当然这里读到的数据可能不是最新的。因为写时复制的思想是通过延时更新的策略来实现数据的最终一致性的并非强一致性。
所以 CopyOnWrite 容器是一种读写分离的思想读和写不同的容器。而 Vector 在读写的时候使用同一个容器读写互斥同时只能做一件事儿。