0x4、fail-fast(快速失败)
从上面我们知道了异常发生的原因了,那为何要这样设计呢?这种玩法有个专业名词 →「fail-fast(快速失败)机制」
在做系统设计的时候先考虑异常情况,一旦发生异常,直接停止并上报。
举个简单的例子,写个两数相除的方法,如果不小心除以0,运行时就会引发异常ArithmeticException by/zero,而及时失败,则是在执行运算前,检测被除数是否为0,是直接抛出异常。示例如下:
行吧,一种检测Bug机制,用于提前预警,那ArrayList里为何要用到这种机制呢?
→ 因为ArrayList不是线程安全的,多个线程同时访问同一个ArrayList可能会引起异常;
→ 引入及时失败是想着:若某个线程通过Iterator遍历时,集合内容被其他线程改变,抛出异常;
在上面的例子中,我们利用迭代器遍历remove的方式来规避ConcurrentModificationException,但那是只有在单个线程访问列表,如果是有多个线程在访问呢?写个例子试试:
行吧,抛异常了,那有在多线程的时候有什么办法规避呢?换个线程安全的容器,比如:Vector?
一样会抛异常,Vector虽然采用synchronized进行同步,但还是继承自AbstarctList,直接通过Iterator即可访问容器元素,根本不需要获取锁。
哪有解决方法吗?
有,直接在使用iterator处加锁即可,代码示例如下:
可以,那除了对iterator加锁的方式外,还有其他方法吗?
有,上并发容器,它采用的是fail-safe(安全失败)机制:迭代时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历,遍历期间原集合发生的修改迭代器是不知道的,以此避免了ConcurrentModificationException,但同样的迭代器不能访问到修改后的内容。Java中的并发容器放在java.util.concurrent包中,比如可以使用CopyOnWriteArrayList来代替ArrayList和Vector。
0x5、Kotlin中toList()的原理
差点漏了,那位盘友问的为啥加个toList()就可以了,代码示例如下:
跟下源码:
传入的集合类型,大小为5,走else,进toMutableList
啧啧,看到这里,你get到原理没?
- 直接创建了一个新的ArrayList,把原列表作为参数传入初始化;
- 拿新列表的迭代器遍历,移除的是旧列表中的元素,肯定互不影响啊,和fail-safe(安全失败)的玩法如出一辙。