如题,最近项目里有个模块我做了异步处理方面的事情,在code过程中发现一个颠覆我对synchronized这个关键字和用法的地方,请问各位java开发者们是否对此有一个合理的解释,不多说,我直接贴出问题代码:
(事实证明这是一个坑,各位读者,如果有兴趣,可以先不看答案,自己看看能不能发现这个坑)
importjava.util.ArrayList;importjava.util.Collections;importjava.util.List;importjava.util.concurrent.CopyOnWriteArrayList;public classConcurrentList {//private static List TEST_LIST = new CopyOnWriteArrayList();
private static List TEST_LIST = Collections.synchronizedList(new ArrayList());public static voidmain(String[] args) {new Thread(newRunnable() {
@Overridepublic voidrun() {while (true) {try{
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}synchronized(TEST_LIST) {
TEST_LIST.add("11");
}
System.out.println("Thread1 running");
}
}
}).start();new Thread(newRunnable() {
@Overridepublic voidrun() {while (true) {try{
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}synchronized(TEST_LIST) {for(String at : TEST_LIST) {
TEST_LIST.add("22");
}
}
System.out.println("Thread2 running");
}
}
}).start();
}
}
输出结果是:
Thread1 running
Exception in thread"Thread-1"java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at com.free4lab.lol.ConcurrentList$2.run(ConcurrentList.java:40)
at java.lang.Thread.run(Thread.java:619)
Thread1 running
Thread1 running
Thread1 running
Thread1 running
Thread1 running
Thread1 running
Thread1 running
Thread1 running
-----------------------------------分隔线,以下是解释--------------------------------
问题明了了:
以上问题不是并发的问题,是ArrayList的问题,是个坑!且看如下代码,以及运行结果:
importjava.util.ArrayList;importjava.util.Collections;importjava.util.List;importjava.util.concurrent.CopyOnWriteArrayList;public classConcurrentList {//private static List TEST_LIST = new CopyOnWriteArrayList();
private static List TEST_LIST = Collections.synchronizedList(new ArrayList());public static voidmain(String[] args) {
TEST_LIST.add("111");
TEST_LIST.add("222");for(String at : TEST_LIST) {
System.out.println(at);
TEST_LIST.add("333");
System.out.println("add over");
}
}
}
结果是:
111add over
Exception in thread"main"java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at com.free4lab.lol.ConcurrentList.main(ConcurrentList.java:15)
分析:我们发现迭代了一次之后就抛出所谓的并发修改异常,不过这里没有多线程,看下源代码就知道了
list.add的时候执行了,修改了modCount,循环外面一次add到第一次迭代不会有问题,因为初始化的时候在AbstractList中int expectedModCount = modCount;,
/*** Appends the specified element to the end of this list.
*
*@parame element to be appended to this list
*@returntrue (as specified by {@linkCollection#add})*/
public booleanadd(E e) {
ensureCapacity(size+ 1); //Increments modCount!!
elementData[size++] =e;return true;
}public void ensureCapacity(intminCapacity) {
modCount++;int oldCapacity =elementData.length;if (minCapacity >oldCapacity) {
Object oldData[]=elementData;int newCapacity = (oldCapacity * 3)/2 + 1;if (newCapacity
newCapacity=minCapacity;//minCapacity is usually close to size, so this is a win:
elementData =Arrays.copyOf(elementData, newCapacity);
}
}
publicE next() {
checkForComodification();try{
E next=get(cursor);
lastRet= cursor++;returnnext;
}catch(IndexOutOfBoundsException e) {
checkForComodification();throw newNoSuchElementException();
}
}
这样迭代器next()第一次 checkForComodification() 是不会抛出异常的,第二次才会抛出异常,因为在checkForComodification()里检查了
final voidcheckForComodification() {if (modCount !=expectedModCount)throw newConcurrentModificationException();
}
}
这样,在循环迭代中,进行了一次add操作,修改了modcount变量,再次迭代的时候,异常就throw出来了!
如果非要进行这样的操作,那么声明list为CopyOnWriteArrayList,就ok!因为用了copyonwrite技术
importjava.util.ArrayList;importjava.util.Collections;importjava.util.List;importjava.util.concurrent.CopyOnWriteArrayList;public classConcurrentList {private static List TEST_LIST = new CopyOnWriteArrayList();//private static List TEST_LIST = Collections.synchronizedList(new ArrayList());
public static voidmain(String[] args) {
TEST_LIST.add("111");
TEST_LIST.add("222");for(String at : TEST_LIST) {
System.out.println(at);
TEST_LIST.add("333");
System.out.println("add over");
}
}
}
输出是正确的:
111add over222add over
额外再说一点,也可以用iterator迭代,不过同样也无法调用next()方法(我注释掉了),这样程序就是死循环了,不断的加,不断的迭代。所以我感觉如果需要在迭代中增加元素,真正有用的还是CopyOnWriteArrayList,不过实际中,如果CopyOnWriteArrayList代价太高,可能我们可以申请一个临时list存放,在迭代后合并到主list中!
importjava.util.ArrayList;importjava.util.Collections;importjava.util.Iterator;importjava.util.List;importjava.util.concurrent.CopyOnWriteArrayList;public classConcurrentList {//private static List TEST_LIST = new CopyOnWriteArrayList();
private static List TEST_LIST = Collections.synchronizedList(new ArrayList());public static voidmain(String[] args) {
TEST_LIST.add("111");
TEST_LIST.add("222");
Iterator iterator=TEST_LIST.iterator();while(iterator.hasNext()){//System.out.println(iterator.next());
TEST_LIST.add("333");
System.out.println("add over");
}
}
}