List接口

List接口继承自Collection接口。在List集合中允许出现重复的元素,所有的元素是以一种线性的元素存储的。

ArrayList集合

ArrayList是 List 接口的一个实现类。可以将 ArrayList集合看作是一个长度可变的数组。

由于 ArrayList集合的底层是使用一个数组来保存元素,在增加和删除元素时,会导致创建新的数组,效率比较低,所有不适合做大量的增删操作。

但这种数组的结构允许程序通过索引的方式来访问元素,因此使用ArrayList集合查找元素很方便。

LinkedList集合

LinkedList集合内部维护了一个双向循环链表。

链表中的每一个元素都使用引用的方式来记住它的前一个和后一个元素,从而可以将所有的元素连接起来。

所以LinkedList集合对于元素的增删操作具有很高的效率。

Vector集合

在书上是这样描述 Vector集合的:用法和 ArrayList 完全一样,区别在于 Vector集合是线程安全的,而 ArrayList集合是线程不安全的。

所以我去搜了下 Vector集合是如何实现线程安全的。然后发现 Vector集合并不是绝对的线程安全,至少在进行复合操作时,并不是线程安全的。

举个栗子:

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
public class App {
public static void main(String[] args) {
// ArrayList<String> list = new ArrayList<>();
Vector<String> list = new Vector<>();
list.add("1");
list.add("2");

new Thread(new Runnable() {
public void run() {
//集合大小
int len = list.size();
try {
//睡5s
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//删除最后一个
list.remove(len-1);
}
}, "t1").start();

new Thread(new Runnable() {
public void run() {
//清空集合
list.clear();
}
}, "t2").start();
}
}

运行报错,分析原因:

线程t1先执行,获取到的list的size为2, 暂停5s, 线程t2开始执行, 清空list集合, 线程t1休眠时间结束,此时再删除就出现数组越界.因为数据已清空.

线程t1执行list.size()方法,此时线程t1持有list对象锁.其他线程等待. 线程t1执行完list.size方法之后会释放list对象锁. 之后进入休眠. 线程t2获取list对象锁后, 遍可以操作list, 而一旦线程t2操作了list对象, 那数组越界问题就出现了. 所以说, list.size 跟 list.remove 这2个方法单独操作时,是线程安全的。一旦同时操作,那vector就不是大家所认为的线程安全操作了.

解决方法,可以尝试加锁:

1
2
3
4
5
6
public void deleteLast() {
synchronized (v) {
int index = v.size() - 1;
v.remove(index);
}
}

如上,对v进行加锁,即可保证同一时刻,不会有其他线程删除掉v中的元素。

总结:中的所有自带方法都是线程安全的,因为方法都使用synchronized关键字标注。但是,对这些集合类的复合操作无法保证其线程安全性。需要客户端通过主动加锁来保证。

0%