CopyOnWriteArrayList
参考资料:
JDK 文档
CoppyOnWriteArrayList api 说明:
ArrayList的一个线程安全的变体,其中所有可变操作(add、set 等等)都是通过对底层数组进行一次新的复制来实现的。
这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法更 有效。在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。“快照”风格的迭代器方法在创建迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内不会更改,因此不可能发生冲突,并且迭代器保证不会抛出 ConcurrentModificationException。创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。在迭代器上进行的元素更改操作(remove、set 和 add)不受支持。这些方法将抛出 UnsupportedOperationException。
允许使用所有元素,包括 null。
内存一致性效果:当存在其他并发 collection 时,将对象放入 CopyOnWriteArrayList
之前的线程中的操作Happen-Before随后通过另一线程从 CopyOnWriteArrayList
中访问或移除该元素的操作。
解决问题: 但选择使用ArrayList时候, 经常能够在如下示例代码中,在多线程的环境下抛出ConcurrentModificationException:
示例代码 01:
private ReentrantLock lock = new ReentrantLock();
private volatile boolean modified = false;
private Condition modifiedCondition = lock.newCondition();
private ArrayList<String> arrayList = null;
public ArrayListCache(ArrayList<String> list){
this.arrayList = list;
}
public void iterateList(){
lock.lock();
try{
Iterator<String> iterator = arrayList.iterator();
while(! modified){
modifiedCondition.await();
}
while(iterator.hasNext()){
String item = iterator.next();
//doSomeThing
}
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void addToCache(String item){
lock.lock();
try{
arrayList.add(item);
modified = true;
modifiedCondition.signal();
}finally{
lock.unlock();
}
}
上述代码中,可能会抛出ConcurrentModificationException异常. 这是因为当thread_1在得到当前的arrayList的iterator之后会等待thread_2去调用addToCache方法, 当thread_2更改了list并signal thread_1之后, 由于arrayList被改变, iterator.next()就会抛出异常; 可以给出iterator.next()的源代码如下:
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
上述的
checkForComodification();
将会抛出此异常;
在这种情况下, 可以考虑使用CopyOnWriteArrayList来代替ArrayList.更改示例代码:
private ReentrantLock lock = new ReentrantLock();
private volatile boolean modified = false;
private Condition modifiedCondition = lock.newCondition();
private CopyOnWriteArrayList<String> arrayList = null;
public ArrayListCache(ArrayList<String> list){
if(list != null){
this.arrayList = new CopyOnWriteArrayList<String>(list);
}else{
//do ypu code if the parameter is null
}
}
CopyOnWriteArrayList处理更改操作的时候,会先复制一份,也就是说实在一个copy里面进行的,更改之后将会set到原来的引用上,如下式CopyOnWriteArrayList add方法的源代码参考:
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray(); //get the orig arrayList
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements); //set after update
return true;
} finally {
lock.unlock();
}
to be continue ...