【引言】针对集合实际上我们有一个专题有了一个基础的说明,但是还不够深入,所以,借着这个源码畅读系列的整理的机会,在这里好好的读读几个重点集合类的源码,第一个最常接触的集合类就是ArrayList了,所以就从它开始了!
类的定义
1 | /** |
成员变量
1 | /** |
构造方法
ArrayList()
1 | /** |
ArrayList(int initialCapacity)
1 | /** |
ArrayList(Collection<? extends E> c)
1 | /** |
增
add
1 | /** |
删
remove
1 | /** |
clear
1 | /** |
改
set
1 | /** |
查
indexOf
1 | /** |
forEach
1 | /** |
get
1 | /** |
其他方法
sort
1 | /** |
toArray
1 | /** |
内部类
Itr
1 | /** |
ListItr
1 | /** |
关于modCount
基本说明
在前面一章的add方法中,我们第一次注意到一个来自于AbstractList的属性modCount,这个值呢是用来记录ArrayList结构变化次数的,也就是说add()、remove()、addAll()、removeRange()及clear()这些方法,每调用一次,modCount的值就加1。
那么这个值有什么作用呢?官话说是jdk在面对迭代遍历的时候为了避免不确定性而采取的快速失败原则。看上去说的简单明了,但对于这个概念还是很模糊。
我们知道,该字段被Iterator以及ListIterator的实现类所使用,如果该值被意外更改,Iterator或者ListIterator 将抛出ConcurrentModificationException异常;那么我们就结合ArrayList内的代码实现看看是怎么用的,想必就清楚了这个值是干什么的了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/**
* 【Lin.C】:重点是第一行,也就是通常通过迭代器操作时,第一行都会走这个checkForComodification
*/
public E next() {
checkForComodification();
......
}
/**
* 【Lin.C】:这里的实现也很简要,就是判断当前的modCount和我期望的是不是一致,不一致就抛异常(够暴力)
*/
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
ConcurrentModificationException
从上面的一节,我们已经发现当迭代集合时,某些不当的操作会引发ConcurrentModificationException,那具体哪几种操作会引发这个异常呢?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94/**
* 【Lin.C】:出异常的第一种情况;通过iterator遍历时,不通过迭代器本身删除集合元素
*/
private static void operate1(List<String> list) {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String temp = iterator.next();
if (iterator.next().equals("C")) {
// 【Lin.C】:这个操作可以正确移除集合元素
//iterator.remove();
// 【Lin.C】:这个操作会抛出ConcurrentModificationException
list.remove(temp);
}
}
}
/**
* 【Lin.C】:出异常的第二种情况,在循环中直接remove元素
*/
private static void operate2(List<String> list) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
int i = 1;
for (String v : list) {
if ("A".equals(v)) {
list.remove(v);
}
}
}
/**
* 【Lin.C】:出异常的第三种情况,多线程操作同一个集合时
*/
private static void operate3(final List<String> list) {
// 【Lin.C】:第一个线程,遍历集合,每次休眠10ms
new Thread(new Runnable() {
public void run() {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
// 【Lin.C】:第二个线程,遍历同一个集合,然后做一个条件remove
new Thread(new Runnable() {
public void run() {
// 【Lin.C】:注意,这种遍历方式,本身是不会出现异常的
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals("A")) {
list.remove(i);
}
}
}
}).start();
}
/**
* 【Lin.C】:不出异常的第一种情况,简单的for循环遍历(适用于增删集合元素)
*/
private static void operate4(List<String> list) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
for (int i = 1; i < list.size(); i++) {
if (i == 1) {
list.remove(list.get(i));
}
}
}
/**
* 【Lin.C】:不出异常的第二种情况,迭代器操作
*/
private static void operate5(List<String> list) {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
if (iterator.next().equals("1")) {
iterator.remove();
}
}
}
异常过程追踪
针对上一节提到的ConcurrentModificationException,我们已经总结了某些场景下会出现这个异常,但是为什么出现呢?通过一次debug我们来了解一下里面的玄机。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/**
* 测试Demo
* @param args
*/
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
int i = 1;
for (String v : list) {
if (i++ == 1) {
// 【Lin.C】:这个操作是会返回true的
System.out.println(list.remove(v));
}
}
}
remove的流程是怎么走的?
1 | - list.remove(v);发起调用 |
异常是如何引发的呢?
1 | - 按照上面的删除逻辑,实际上程序走到remove完成这一步是不会报异常的;而且删除操作本身也是成功的 |
普通for和迭代器为什么不会引发异常?
1 | - 普通for循环:因为这种for直接通过下标索引,不会走到itr内部的next,也就不会触发checkForComodification |
扩展:Vector
前面我们就发现了,ArrayList本身是线程不安全的,所以有了Vector;Vector就是线程安全版本的数组容器。Vector的实现很简单,就是把所有的方法统统加上synchronized,通过下面的方法对比图也很明显可以看得出来这个特征。
也可以不使用Vector,用Collections.synchronizedList把一个普通ArrayList包装成一个线程安全版本的数组容器也可以,原理同Vector是一样的,就是给所有的方法套上一层synchronized。(关于Collections后面会再单独列一篇文章解读)