java 线程同步的list_java 多线程操作List,已经做了同步synchronized,还会有ConcurrentModificationException,知道为什么吗?...

如题,最近项目里有个模块我做了异步处理方面的事情,在code过程中发现一个颠覆我对synchronized这个关键字和用法的地方,请问各位java开发者们是否对此有一个合理的解释,不多说,我直接贴出问题代码:

(事实证明这是一个坑,各位读者,如果有兴趣,可以先不看答案,自己看看能不能发现这个坑)

48304ba5e6f9fe08f3fa1abda7d326ab.png

import java.util.ArrayList;

import java.util.Collections;

import java.util.List;

import java.util.concurrent.CopyOnWriteArrayList;

public class ConcurrentList {

//private static List TEST_LIST = new CopyOnWriteArrayList();

private static List TEST_LIST = Collections.synchronizedList(new ArrayList());

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();

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

输出结果是:

48304ba5e6f9fe08f3fa1abda7d326ab.png

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

48304ba5e6f9fe08f3fa1abda7d326ab.png

-----------------------------------分隔线,以下是解释--------------------------------

问题明了了:

以上问题不是并发的问题,是ArrayList的问题,是个坑!且看如下代码,以及运行结果:

48304ba5e6f9fe08f3fa1abda7d326ab.png

import java.util.ArrayList;

import java.util.Collections;

import java.util.List;

import java.util.concurrent.CopyOnWriteArrayList;

public class ConcurrentList {

//private static List TEST_LIST = new CopyOnWriteArrayList();

private static List TEST_LIST = Collections.synchronizedList(new ArrayList());

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");

}

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

结果是:

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;,

48304ba5e6f9fe08f3fa1abda7d326ab.png

/**

* Appends the specified element to the end of this list.

*

* @param e element to be appended to this list

* @return true (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);

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

48304ba5e6f9fe08f3fa1abda7d326ab.png

public E next() {

checkForComodification();

try {

E next = get(cursor);

lastRet = cursor++;

return next;

} catch (IndexOutOfBoundsException e) {

checkForComodification();

throw new NoSuchElementException();

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

这样迭代器next()第一次 checkForComodification() 是不会抛出异常的,第二次才会抛出异常,因为在checkForComodification()里检查了

final void checkForComodification() {

if (modCount != expectedModCount)

throw new ConcurrentModificationException();

}

}

这样,在循环迭代中,进行了一次add操作,修改了modcount变量,再次迭代的时候,异常就throw出来了!

如果非要进行这样的操作,那么声明list为CopyOnWriteArrayList,就ok!因为用了copyonwrite技术

48304ba5e6f9fe08f3fa1abda7d326ab.png

import java.util.ArrayList;

import java.util.Collections;

import java.util.List;

import java.util.concurrent.CopyOnWriteArrayList;

public class ConcurrentList {

private static List TEST_LIST = new CopyOnWriteArrayList();

//private static List TEST_LIST = Collections.synchronizedList(new ArrayList());

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");

}

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

输出是正确的:

111

add over

222

add over

额外再说一点,也可以用iterator迭代,不过同样也无法调用next()方法(我注释掉了),这样程序就是死循环了,不断的加,不断的迭代。所以我感觉如果需要在迭代中增加元素,真正有用的还是CopyOnWriteArrayList,不过实际中,如果CopyOnWriteArrayList代价太高,可能我们可以申请一个临时list存放,在迭代后合并到主list中!

48304ba5e6f9fe08f3fa1abda7d326ab.png

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 TEST_LIST = new CopyOnWriteArrayList();

private static List TEST_LIST = Collections.synchronizedList(new ArrayList());

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");

}

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值