列表实现有ArrayList、Vector、CopyOnWriteArrayList、Collections.synchronizedList(list)四种方式。
1 ArrayList
ArrayList是非线性安全,此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:在建立迭代器以后,除非经过迭代器自身的 remove 或 add 方法从结构上对列表进行修改,不然在任什么时候间以任何方式对列表进行修改,迭代器都会抛出 ConcurrentModificationException。即在一方在便利列表,而另外一方在修改列表时,会报ConcurrentModificationException错误。而这不是惟一的并发时容易发生的错误,在多线程进行插入操做时,因为没有进行同步操做,容易丢失数据。java
- public boolean add(E e) {
- ensureCapacity(size + 1);
- elementData[size++] = e;
- return true;
- }
所以,在开发过程中,ArrayList并不适用于多线程的操做。
2 Vector
3 Collections.synchronizedList & CopyOnWriteArrayList
CopyOnWriteArrayList和Collections.synchronizedList是实现线程安全的列表的两种方式。两种实现方式分别针对不一样状况有不一样的性能表现,其中CopyOnWriteArrayList的写操做性能较差,而多线程的读操做性能较好。而Collections.synchronizedList的写操做性能比CopyOnWriteArrayList在多线程操做的状况下要好不少,而读操做由于是采用了synchronized关键字的方式,其读操做性能并不如CopyOnWriteArrayList。所以在不一样的应用场景下,应该选择不一样的多线程安全实现类。
3.1 Collections.synchronizedList
Collections.synchronizedList的源码可知,其实现线程安全的方式是创建了list的包装类,代码以下:
- public static <T> List<T> synchronizedList(List<T> list) {
- return (list instanceof RandomAccess ?
- new SynchronizedRandomAccessList<T>(list) :
- new SynchronizedList<T>(list));
- }
其中,SynchronizedList对部分操做加上了synchronized关键字以保证线程安全。但其iterator()操做还不是线程安全的。部分SynchronizedList的代码以下:
- public E get(int index) {
- synchronized(mutex) {return list.get(index);}
- }
- public E set(int index, E element) {
- synchronized(mutex) {return list.set(index, element);}
- }
- public void add(int index, E element) {
- synchronized(mutex) {list.add(index, element);}
- }
- public ListIterator<E> listIterator() {
- return list.listIterator();
- }
-
- public ListIterator<E> listIterator(int index) {
- return list.listIterator(index);
- }
从字面能够知道,CopyOnWriteArrayList在线程对其进行些操做的时候,会拷贝一个新的数组以存放新的字段。其写操做的代码以下:
- transient final ReentrantLock lock = new ReentrantLock();
-
-
- private volatile transient Object[] array;
-
- public boolean add(E e) {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- Object[] elements = getArray();
- int len = elements.length;
- Object[] newElements = Arrays.copyOf(elements, len + 1);
- newElements[len] = e;
- setArray(newElements);
- return true;
- } finally {
- lock.unlock();
- }
- }
其读操做代码以下:
- public E get(int index) {
- return (E)(getArray()[index]);
- }
其没有加任何同步关键字,根据以上写操做的代码可知,其每次写操做都会进行一次数组复制操做,而后对新复制的数组进行些操做,不可能存在在同时又读写操做在同一个数组上(不是同一个对象),而读操做并无对数组修改,不会产生线程安全问题。Java中两个不一样的引用指向同一个对象,当第一个引用指向另一个对象时,第二个引用还将保持原来的对象。
其中setArray()操做仅仅是对array进行引用赋值。Java中“=”操做只是将引用和某个对象关联,假如同时有一个线程将引用指向另一个对象,一个线程获取这个引用指向的对象,那么他们之间不会发生ConcurrentModificationException,他们是在虚拟机层面阻塞的,并且速度很是快,是一个原子操做,几乎不须要CPU时间。
在列表有更新时直接将原有的列表复制一份,并再新的列表上进行更新操做,完成后再将引用移到新的列表上。旧列表若是仍在使用中(好比遍历)则继续有效。如此一来就不会出现修改了正在使用的对象的状况(读和写分别发生在两个对象上),同时读操做也没必要等待写操做的完成,免去了锁的使用加快了读取速度。
3.3 Collections.synchronizedList & CopyOnWriteArrayList在读写操做上的差距
测试代码:
|
写操做 |
读操做 |
|
CopyOnWriteArrayList |
Collections. synchronizedList |
CopyOnWriteArrayList |
Collections. synchronizedList |
2 |
567 |
2 |
1 |
1 |
4 |
3088 |
3 |
2 |
2 |
8 |
25975 |
28 |
2 |
3 |
16 |
295936 |
44 |
2 |
6 |
32 |
- |
- |
3 |
8 |
64 |
- |
- |
7 |
21 |
128 |
- |
- |
9 |
38 |
写操做:在线程数目增长时CopyOnWriteArrayList的写操做性能降低很是严重,而Collections.synchronizedList虽然有性能的下降,但降低并不明显。
读操做:在多线程进行读时,Collections.synchronizedList和CopyOnWriteArrayList均有性能的下降,可是Collections.synchronizedList的性能下降更加显著。
4 结论
CopyOnWriteArrayList,发生修改时候作copy,新老版本分离,保证读的高性能,适用于以读为主,读操做远远大于写操做的场景中使用,好比缓存。而Collections.synchronizedList则能够用在CopyOnWriteArrayList不适用,可是有须要同步列表的地方,读写操做都比较均匀的地方。