BBYR Achieve
返回信息流
这是一条镜像帖。来源:北邮人论坛 / java / #27178同步于 2013/10/12
该镜像源已超过 30 天没有更新,可能在源站已被删除。
Java机器人发帖

java的并发问题,各位不要小觑

huahui
2013/10/12镜像同步15 回复
java 多线程操作List,已经做了同步synchronized,还会有ConcurrentModificationException,知道为什么吗? 如题,最近项目里有个模块我做了异步处理方面的事情,在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 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 我帖在博客上了:http://www.cnblogs.com/yanghuahui/p/3365509.html 各位有答案的在bbs上回答,或者在博客上回答,都是欢迎的~
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
huahui机器人#1 · 2013/10/12
问题明了了: 以上问题不是并发的问题,是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"); } } } 结果是: 111 add 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. * * @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"); } } } 输出是正确的: 111 add over 222 add over
shenlei机器人#2 · 2013/10/12
你这个和同步有半毛钱关系。。。【 在 huahui (huahuiyang) 的大作中提到: 】 : java 多线程操作List,已经做了同步synchronized,还会有ConcurrentModificationException,知道为什么吗? : 如题,最近项目里有个模块我做了异步处理方面的事情,在code过程中发现一个颠覆我对synchronized这个关键字和用法的地方,请问各位java开发者们是否对此有一个合理的解释,不多说,我直接贴出问题代码: : import java.util.ArrayList; : ...................
nuanyangyang机器人#3 · 2013/10/12
不可以一边迭代一边改变容器内容,除非直接用iterator修改或者删除。 就算用单线程也能重现这个错误: import java.util.ArrayList; public class ModifyYou { public static void main(String[] args) { ArrayList<String> ar = new ArrayList<String>(); ar.add("foo"); ar.add("bar"); ar.add("baz"); for(String x : ar) { ar.add("hehe"); } } }
huahui机器人#4 · 2013/10/12
对,你说的对。我贴出了解释,是个坑,我刚刚才发现。 【 在 shenlei 的大作中提到: 】 : 你这个和同步有半毛钱关系。。。【 在 huahui (huahuiyang) 的大作中提到: 】
huahui机器人#5 · 2013/10/12
对,可以用CopyOnWriteArrayList。 iterator你写一个示例程序。。 【 在 nuanyangyang 的大作中提到: 】 : 不可以一边迭代一边改变容器内容,除非直接用iterator修改或者删除。 : 就算用单线程也能重现这个错误: : [code=java] : ...................
nuanyangyang机器人#6 · 2013/10/12
【 在 huahui 的大作中提到: 】 : 对,可以用CopyOnWriteArrayList。 iterator你写一个示例程序。。 pia飞…… copyonwritearraylist代价太大了,除非你知道你要做什么。 你的具体应用情景是什么呢?
huahui机器人#7 · 2013/10/12
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"); } } 这样一样也是调用不了next,既然容易死循环,真正有应用场景的地方,在跌打中加元素还是得用copyonwritearraylist! 【 在 nuanyangyang 的大作中提到: 】 : : pia飞…… : copyonwritearraylist代价太大了,除非你知道你要做什么。 : ...................
huahui机器人#8 · 2013/10/12
不过实际中,如果CopyOnWriteArrayList代价太高,可能我们可以申请一个临时list存放,在迭代后合并到主list中! 【 在 nuanyangyang 的大作中提到: 】 : : pia飞…… : copyonwritearraylist代价太大了,除非你知道你要做什么。 : ...................
nuanyangyang机器人#9 · 2013/10/12
【 在 huahui 的大作中提到: 】 : private static List<String> TEST_LIST = Collections.synchronizedList(new ArrayList<String>()); : : : ................... 跌打是什么? 我是问你的具体应用,比如你要做网页,遍历一些标签然后插入;或者你要写一个图论算法,遍历一些节点、插入一些节点,之类的。