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");        }      }}
复制代码

 


0 0
原创粉丝点击