众所周知,ArrayList 是线程不安全的。三个解决线程安全的方法:
- 使用 Collections.syncronized(arrayList) ,将集合同步化
- 使用 Vector 替代 ArrayList,其内部涉及到写的方法,都是 syncronized 修饰的同步方法,其他底层原理和 ArrayList 几乎相同。
- 使用 CopyOnWriteArrayList,在写方法的内部加锁,而不是直接在方法上加锁,会轻量级一点,然后增加写时复制逻辑,没有了容量一说。
结论,第三种方法线程安全,效率相对较高,为最优解。可是为啥子?哎~先捡几个简单又能说明问题的方法看看源码再说。。。。
/**
* Gets the array. Non-private so as to also be accessible
* from CopyOnWriteArraySet class.
*/
final Object[] getArray() {
return array;
}
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
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();
}
}
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices). Returns the element that was removed from the list.
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
/**
* Returns the number of elements in this list.
*
* @return the number of elements in this list
*/
public int size() {
return getArray().length;
}
以这几个方法为例,可以看出,不论添加还是移除,集合内都会有一个新的数组 newElements,写操作是针对这个“复制”出来的数组进行的,写完之后,用 setArray 方法将这个“复制”并操作好的数组赋值给 array。所以称为“写时复制”。
内部没有想 ArrayList 那样单独维护一个 size 变量,调用 size() 方法,直接返回 array.length。增减元素,也不涉及 size 的加减了。每次增加元素都“复制”出一个长度为 array.length + 1 的数组,每次移除元素都“复制”一个长度为 array.length - 1 的数组。
这三种方案的线程安全,都指的是单个方法是线程安全的,比如多个线程单纯的用 add() 增加元素,可以保证线程安全。应注意的是,当多个方方组合判断时,仍会出现安全问题,比如:
if (copyOnWriteArrayList.size() == 0){
copyOnWriteArrayList.add("添加元素");
}
如上,if 和 add 之间并没有保障,所以,不要以为用了这三种方案就万事大吉了,还是要注意安全问题!
CopyOnWriteArrayList 把写方法的锁放在方法内部,这确实比直接加在方法上好。三种方案中推荐使用的是 CopyOnWriteArrayList,但是写时复制,每次都涉及到数组的全量复制,不仅仅占用额外的内存,也造成额外的 CPU消耗。所以 CopyOnWriteArrayList 和 ArrayList 一样,不适合写操作多的场景。
另外,CopyOnWriteArrayList 不能保证实时性,他保证的是最终一致性,例如:a 线程在修改集合,但操作还没完,而 b 线程已经开始读取集合了,在这个时刻,两个线程的数据是不同的,也就是说 b 线程读到的是旧数据。但 a 线程操作完成后,b 再读数据就是新的了。所以,CopyOnWriteArrayList 保证的是最终一致性。
网友评论