java 多线程操作List,已经做了同步synchronized,还会有ConcurrentModificationException,知道为什么吗?
来源:互联网 发布:淘宝二手3c认证 编辑:程序博客网 时间:2024/06/05 08:42
如题,最近项目里有个模块我做了异步处理方面的事情,在code过程中发现一个颠覆我对synchronized这个关键字和用法的地方,请问各位java开发者们是否对此有一个合理的解释,不多说,我直接贴出问题代码:
(事实证明这是一个坑,各位读者,如果有兴趣,可以先不看答案,自己看看能不能发现这个坑)
import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;public class ConcurrentList { //private static List<String> TEST_LIST = new CopyOnWriteArrayList<String>(); private static List<String> TEST_LIST = Collections.synchronizedList(new ArrayList<String>()); public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { 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(new Runnable() { @Override public void run() { 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 runningException 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 runningThread1 runningThread1 runningThread1 runningThread1 runningThread1 runningThread1 runningThread1 running
-----------------------------------分隔线,以下是解释--------------------------------
问题明了了:
以上问题不是并发的问题,是ArrayList的问题,是个坑!且看如下代码,以及运行结果:
import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;public class ConcurrentList { //private static List<String> TEST_LIST = new CopyOnWriteArrayList<String>(); private static List<String> TEST_LIST = Collections.synchronizedList(new ArrayList<String>()); public static void main(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 overException 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. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacity(size + 1); // Increments modCount!! elementData[size++] = e; return true; }public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3)/2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } }
public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } }
这样迭代器next()第一次 checkForComodification() 是不会抛出异常的,第二次才会抛出异常,因为在checkForComodification()里检查了
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }}
这样,在循环迭代中,进行了一次add操作,修改了modcount变量,再次迭代的时候,异常就throw出来了!
如果非要进行这样的操作,那么声明list为CopyOnWriteArrayList,就ok!因为用了copyonwrite技术
import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;public class ConcurrentList { private static List<String> TEST_LIST = new CopyOnWriteArrayList<String>(); //private static List<String> TEST_LIST = Collections.synchronizedList(new ArrayList<String>()); public static void main(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中!
import java.util.ArrayList;import java.util.Collections;import java.util.Iterator;import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;public class ConcurrentList { //private static List<String> TEST_LIST = new CopyOnWriteArrayList<String>(); private static List<String> TEST_LIST = Collections.synchronizedList(new ArrayList<String>()); public static void main(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"); } }}
- java 多线程操作List,已经做了同步synchronized,还会有ConcurrentModificationException,知道为什么吗?
- java Synchronized 多线程同步
- java多线程同步,Synchronized
- Java 多线程同步--synchronized
- Java 多线程 synchronized同步
- java多线程同步 synchronized
- Java使用synchronized实现多线程操作list<1>
- Java多线程同步Synchronized详解
- Java多线程同步 synchronized关键字
- Java多线程同步与synchronized
- Java多线程同步 synchronized使用
- Java 多线程:synchronized 多线程同步关键字
- Java 多线程:synchronized 多线程同步关键字
- 怎么知道java多线程已经运行结束
- Java多线程入门:Synchronized同步类方法
- Java 多线程同步 锁机制与synchronized
- Java 多线程同步 锁机制与synchronized
- Java多线程同步Synchronized使用分析
- db2 报错 Deadlock Rate
- NYOJ 12 喷水装置(二)
- MongoDB 自动分片 auto sharding(推荐)
- CAS证书生成的一些注意要点
- 如何启动eclipse时提示选择工作空间||删除workspace空间的目录||快捷方式指向workspace
- java 多线程操作List,已经做了同步synchronized,还会有ConcurrentModificationException,知道为什么吗?
- 3 -- 交换排序之冒泡排序
- 51 css 图片文字同时垂直居中 z-index无效 父级元素塌陷 图片宽度自适应
- android面试题(一)
- 浅谈Hadoop
- VS2010 VS2012调试时显示加载符号导致调试特别慢
- 64位 win8系统 Matplotlib 安装详解
- hibernate.hbm2ddl.auto
- Ubuntu 安装terminator 后更改默认终端设置