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

时间:2023-02-20 15:56: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 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的问题,是个坑!且看如下代码,以及运行结果:

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

额外再说一点,也可以用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");
}
}
}

萌萌的IT人,IT人的乐园

java 多线程操作List,已经做了同步synchronized,还会有ConcurrentModificationException,知道为什么吗?的更多相关文章

  1. &OpenCurlyDoubleQuote;全栈2019”Java多线程第十六章:同步synchronized关键字详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  2. java 多线程&colon; Thread 并发访问-代码块同步synchronized &lbrace;&rcub;;String作为被锁的对象

    方法同步的弊端 方法同步的时候,如果一个方法需要线程安全控制的代码速度其实很快,但是还有其他的业务逻辑代码耗时非常长(比如网络请求),这样所有的线程就在这一块就等待着了,这样造成了极大的资源浪费如果并 ...

  3. Java多线程02&lpar;线程安全、线程同步、等待唤醒机制&rpar;

    Java多线程2(线程安全.线程同步.等待唤醒机制.单例设计模式) 1.线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量 ...

  4. Java 多线程基础(五)线程同步

    Java 多线程基础(五)线程同步 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题. 要解决上述多线程并发访问一个资源的安全性问题,Java中提供了同步机制 ...

  5. Java多线程操作同一个对象,线程不安全

    Java多线程操作同一个对象 发现问题:多个线程操作同一资源的情况下,线程不安全,数据紊乱 代码: package multithreading; // Java多线程操作同一个对象 // 买火车票的 ...

  6. java多线程02-----------------synchronized底层实现及JVM对synchronized的优化

    java多线程02-----------------synchronized底层实现及JVM对synchronized的优化 提到java多线程,我们首先想到的就是synchronized关键字,它在 ...

  7. Java多线程之内存可见性和原子性:Synchronized和Volatile的比较

    Java多线程之内存可见性和原子性:Synchronized和Volatile的比较     [尊重原创,转载请注明出处]http://blog.csdn.net/guyuealian/article ...

  8. (Java多线程系列二)线程间同步

    Java多线程间同步 1.什么是线程安全 通过一个案例了解线程安全 案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果. 先来看一个线程不安全的例子 class Sell ...

  9. java多线程操作

    进程是程序的一次动态的执行过程,它经历了从代码加载.执行完毕的一个完整过程,这个过程也是进程本身从产生.发展到最终消亡的过程. 多线程是实现并发机制的一种有效的手段.进程和线程一样,都是实现并发的一个 ...

  10. Java开发笔记(一百)线程同步synchronized

    多个线程一起办事固然能够加快处理速度,但是也带来一个问题:两个线程同时争抢某个资源时该怎么办?看来资源共享的另一面便是资源冲突,正所谓鱼与熊掌不可兼得,系统岂能让多线程这项技术专占好处?果然是有利必有 ...

随机推荐

  1. javascript之Object&period;defineProperty的奥妙

    直切主题 今天遇到一个这样的功能: 写一个函数,该函数传递两个参数,第一个参数为返回对象的总数据量,第二个参数为初始化对象的数据.如: var o = obj (4, {name: 'xu', age ...

  2. MySQL笔记汇总

    [目录] MySQL笔记汇总 一.mysql简介 数据简介 结构化查询语言 二.mysql命令行操作 三.数据库(表)更改 表相关 字段相关 索引相关 表引擎操作 四.数据库类型 数字型 字符串型 日 ...

  3. tomcat学习笔记一:安装和配置

    安装 配置环境: win7 + centos7虚拟机 安装步骤: 到http://tomcat.apache.org/下载最新的tomcat安装包(8.0.24) 移到对应的目录并解压 安装问题: 安 ...

  4. Jsp页显示时间标签JSTL标签 &lt&semi;fmt&colon;formatDate&sol;&gt&semi; 实例大全

    <fmt:formatDate value="${isoDate}" type="both"/>2004-5-31 23:59:59 <fmt ...

  5. &sol;var&sol;lock&sol;subsys作用

    转自: http://sunxiaqw.blog.163.com/blog/static/9906543820111184422807/ 关于/var/lock/subsys目录 总的来说,系统关闭的 ...

  6. 【html5】这些新类型 能提高生产力

    <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title> ...

  7. 用curl测试rest服务时,

    query parameter总是只能得到第一个,原来是需要加引号,否则url中的&符号被理解为命令行中的后台执行.

  8. Android 日夜间切换Demo

    这是其中一种实现模式,也是比较麻烦的一种,首先写布局,不多说上代码 activity_main.xml <?xml version="1.0" encoding=" ...

  9. pandas 常用技巧总结

    切片: loc:df.loc[num]:选择df 某一行 seriesdf.loc[[num1,num2]]: 选择df 某几行df.loc[[True,False,True,   ,True]]: ...

  10. WPF效果(GIS三维篇)

    二维的GIS已经被我玩烂了,紧接着就是三维了,哈哈!先来看看最简单的效果: