总所周知, ArrayList 是线程不安全的, 而 Vector 的性能太弱了 (直接锁对象), 基本不会使用, JDK 1.5 在 J.U.C 包中提供了一个并发 List : CopyOnWriteArrarList
ArrayList, Vector, LinkedList 三者的区别
ArrayList : 底层是 Object 数组, 使用线性存储方式
特点: 插入慢, 查询快, 线程不安全
Vector : 同 ArrayList 一样 继承了 AbstractList 实现了 List 接口
特点: 线程安全, 使用了同步锁, 但是性能太低, 即使两个线程同时进行读操作都会产生互斥,
但是实际上, 分析源码会发现, 确实很多方法加了 synchronized 同步关键字, 从而保证所有的对外接口方法都会以 Vector 对象为锁, 即 Vector 内部, 所有的方法都不会被多线程访问.
但事实, 单个方法的原子性, 并不能保证复合操作也是原子性的
虽然源代码注释里面说这个是线程安全的,因为确实很多方法都加上了同步关键字 synchronized,但是对于符合操作而言,只是同步方法并没有解决线程安全的问题。要真正达成线程安全,还需要以 vector 对象为锁,来进行操作。所以,如果是这样的话,那么用 vector 和 ArrayList 就没有区别了,所以,不推荐使用 vector。
LinkedList: 底层由链表实现, 使用了链式存储方式
特点: 插入快, 查询慢,线程不安全
ArrayList, LinkedList 集合类都实现了懒加载的方式 (在集合容器初始化的过程中并不会为容器分配内存空间, 只在调用 add 方法的时候才分配内存)
ArrayList 扩容机制
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 位移操作, 相对于 / 操作速度更快
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);// 检查数组长度是否最大限制
/ /使用 Arrays 工具类拷贝数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
选择 1.5 倍的原因是考虑到空间和时间的情况下, 如果太小, 就会在条件元素的时候频繁的进行扩容, 影响程序运行速度, 如果太大, 对内存空间会造成一定的浪费
并且 ArrayLis t的扩容消费相对于 HashMap 更小, 所以允许 ArrayList 更频繁的扩容
使用 ArrayList, 在高并发多线程的情况下, 会抛出 java.util.ConcurrentModificationException 异常
static List<String> list = new ArrayList<>();
public static void main(String[] args) throws Exception {
for (int i = 0; i < 20; i++) {
new Thread(() -> test()).start();
}
}
static void test() {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list); // 抛出 ConcurrentModificationException 异常
}
如何解决ArrayList在多线程的情况下不安全的问题?
-
使用 Vector?
千万不要,,虽然 Vector 在方法上加了锁,保证数据的安全性,但是程序并发执行的效率明显下降, 所以在考虑要保证,数据安全却不考虑执行效率的情况下使用 Vector
-
java.util.Collections 工具类
里面有很多内部类, 其中 synchronizedList 方法接受一个 List,然后返回线程安全的List,可以解决抛出异常的问题(里面锁的是一个 Object mutex 对象), 不止 List, Set 和 Map 同样可以使用这种方式
-
java.util.concurrent.CopyOnWriteArrayList
这个选择才是最优的,读写分离并发容器
CopyOnWriteArrayList
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
.....
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = CopyOnWriteArrayList.class;
lockOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("lock"));
} catch (Exception e) {
throw new Error(e);
}
}
}
查看 CopyOnWriteArrayList 源码,你会发现,里面用到了 CAS,ReentrantLock , 查看他的 add 方法
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();// 锁
try {
Object[] elements = getArray();
int len = elements.length;
// 数组拷贝,并非 1.5 倍扩容,而是将数组长度加一
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
// 覆盖旧的数组
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
往一个容器里添加元素的时候, 不直接往容器中添加,而是将容器复制一份,其长度会加一,可以在新的容器中进行添加元素,添加完元素后,在将原容器的引用更新为新的容器.
这样就可以做到读写分离 (读容器中的数据可以在就容器中完成, 而写操作则是在新的容器中进行)