ArrayList用的太多了,几乎所有人都知道它是线程不安全的,但实际使用中,我们的多线程实现,普遍都是基于一些同步方法或者锁,很多场景其实并不需要关注ArrayList本身的线程安全。网上可以找到三种主流的实现ArrayList线程安全的手段,他们分别是什么样的思路,还是值得简单了解和记录的。
Vector
Vector 是矢量队列,它是JDK1.0版本添加的类,历史比ArrayList(since 1.2)更为悠久。其具体历史已不太可考,个人只能简单猜测是在java初期从其他语言直接借鉴的名字及相关概念。其底层和ArrayList一样是数组,除线程安全外,大多数实现方法和ArrayList逻辑基本一致。值得一提的是,从jdk1.2以后,Java提供了系统的集合框架,就将Vector改为实现List接口,从而导致Vector里有一些重复的方法,例如:addElement(Object obj),实际上这个方法和add(Object obj)没什么区别。
Vector 的实现,就是在方法上都加上synchronized(即使get也不例外)。康康部分源码。
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
关于Vector为什么被弃用
基于集合类的实现,可以简单的使用函数来操作,例如,List
public static List synchronizedList(List list) {
return (list instanceof RandomAccess ?
new Collections.SynchronizedRandomAccessList<>(list) :
new Collections.SynchronizedList<>(list));
}
显然,里面还藏了一些list类,我们以SynchronizedRandomAccessList接着看看这个类是如何操作的。
//Collections中的静态类1号,主要实现在SynchronizedList中
static class SynchronizedRandomAccessList
extends Collections.SynchronizedList
implements RandomAccess {
... //绝大部分方法都没有单独的实现
}
//Collections中的静态类2号
static class SynchronizedList
extends Collections.SynchronizedCollection
implements List {
private static final long serialVersionUID = -7754090372962971524L;
final List list;
...
//可以看到,大部分方法的处理方式,是多了一个synchronized (mutex),
//mutex是SynchronizedCollection中的一个Object变量
public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {
return list.equals(o);
}
}
public int hashCode() {
synchronized (mutex) {
return list.hashCode();
}
}
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 E remove(int index) {
synchronized (mutex) {
return list.remove(index);
}
}
public int indexOf(Object o) {
synchronized (mutex) {
return list.indexOf(o);
}
}
public int lastIndexOf(Object o) {
synchronized (mutex) {
return list.lastIndexOf(o);
}
}
public boolean addAll(int index, Collection extends E> c) {
synchronized (mutex) {
return list.addAll(index, c);
}
}
...
//官方注释明确提出,对于使用 Iterator遍历列表时,Collections.synchronizedList可能发生错误
//还需要手动去确保线程安全
public ListIterator listIterator() {
return list.listIterator(); // Must be manually synched by user
}
public ListIterator listIterator(int index) {
return list.listIterator(index); // Must be manually synched by user
}
}
CopyOnWriteArrayList是1.5后引入,属于JUC的一部分。他基本的原理还是和ArrayList一样,涉及线程安全的部分,是通过写时复制的方式来实现(从名字中就可以看出)。它内部有个volatile数组来保持数据。在“添加/修改/删除”数据时,会先获取互斥锁,再新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给volatile数组,然后再释放互斥锁。以get和set为例看看代码。
public class CopyOnWriteArrayList
implements List, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8673264195747942595L;
//互斥锁
final transient ReentrantLock lock = new ReentrantLock();
//存储数据的volatile数组,入口仅限于类中函数getArray/setArray。
private transient volatile Object[] array;
public E get(int index) {
return get(getArray(), index);
}
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
if (oldValue != element) {
int len = elements.length;
//直接开始复制一格数组,修改某个值,将型数组赋值给CopyOnWriteArrayList中的array
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
// 官方介绍为,并非无效的操作,是为了保证volatile的写语义。
// 这里有点搞心态,翻译一下参考文档6,按照其中一个回答,这里的代码jdk 8中存在,jdk 11中已经移除。但我在jdk 14中又再次发现。
// 参考热门回答,主要是为了确保一些逻辑中CopyOnWriteArrayList外的非 volatile变量,由于指令重排导致的执行顺序问题。
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
}
因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大。
我尝试的代码主要来自参考文档4,就不再贴出来了。
CopyOnWriteArrayList add method cost time is 17156
Collections.synchronizedList add method cost time is 23
Vector add method cost time is 5
CopyOnWriteArrayList get method cost time is 2
Collections.synchronizedList get method cost time is 4
Vector get method cost time is 5
---- 10万级数据
CopyOnWriteArrayList add method cost time is 133
Collections.synchronizedList add method cost time is 1
Vector add method cost time is 0
CopyOnWriteArrayList get method cost time is 0
Collections.synchronizedList get method cost time is 0
Vector get method cost time is 1
---- 万级数据
以上代码又多跑了几次,大同小异。
结论:
1. Java集合(五)应该弃用的Vector和Stack,Java集合(五)应该弃用的Vector和Stack_weixin_33695082的博客-CSDN博客
2. vector过时的代替建议,vector过时的代替建议_忧伤的可乐鸡-CSDN博客_vector的替代
3. 简单理解Collections.synchronizedList,简单理解Collections.synchronizedList_walker-CSDN博客_collections.synchronizedlist
4. CopyOnWriteArrayList与Collections.synchronizedList的性能对比,https://blog.csdn.net/yangzl2008/article/details/39456817
5. Java多线程系列--“JUC集合”02之 CopyOnWriteArrayList,Java多线程系列--“JUC集合”02之 CopyOnWriteArrayList - 如果天空不死 - 博客园
6. Why setArray() method call required in CopyOnWriteArrayList,java - Why setArray() method call required in CopyOnWriteArrayList - Stack Overflow