外部学习资料地址
ArrayList(数组)
一、描述
1. ArrayList 是最常用的List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。
2. 数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。
3. 当从ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
二. 线程安全问题
有两个 先上代码
1. 在多个线程进行add操作时可能会导致elementData数组越界
1. 列表大小为9,即size=9
2. 线程A开始进入add方法,这时它获取到size的值为9,调用ensureCapacityInternal方法进行容量判断。
3. 线程B此时也进入add方法,它获取到size的值也为9,也开始调用ensureCapacityInternal方法。
4. 线程A发现需求大小为10,而elementData的大小就为10,可以容纳。于是它不再扩容,返回。
5. 线程B也发现需求大小为10,也可以容纳,返回。
6. 线程A开始进行设置值操作, elementData[size++] = e 操作。此时size变为10。
7. 线程B也开始进行设置值操作,它尝试设置elementData[10] = e,而elementData没有进行过扩容,它的下标最大为9。于是此时会报出一个数组越界的异常ArrayIndexOutOfBoundsException.
2. 一个线程的值覆盖另一个线程添加的值
1. 列表大小为0,即size=0
2. 线程A开始添加一个元素,值为A。此时它执行第一条操作,将A放在了elementData下标为0的位置上。
3. 接着线程B刚好也要开始添加一个值为B的元素,且走到了第一步操作。此时线程B获取到size的值依然为0,于是它将B也放在了elementData下标为0的位置上。
4. 线程A开始将size的值增加为1
5. 线程B开始将size的值增加为2
- 多线程情况下 操作同一个集合
Vector(数组实现,线程同步)
- Vector 与ArrayList 一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList 慢。
LinkList(链表)
- LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。
- 另外,他还提供了List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
解决线程不安全的问题
解决 线程不安全的问题 都可以用
1. Collections.synchronizedList();
Collections.synchronizedSet();
2. 使用 Vector