如题,最近项目里有个模块我做了异步处理方面的事情,在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. “全栈2019”Java多线程第十六章:同步synchronized关键字详解

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

  2. java 多线程: Thread 并发访问-代码块同步synchronized {};String作为被锁的对象

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

  3. Java多线程02(线程安全、线程同步、等待唤醒机制)

    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. Java实现颜色渐变效果

    RGB色彩,在自然界中肉眼所能看到的任何色彩都可以由红(R).绿(G).蓝(B)这三种色彩混合叠加而成,因此我们只要递增递减的修改其特定值就能得到相应的渐变效果. 运行效果:(图1) 运行5秒后:(图 ...

  2. NYOJ题目813对决

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAssAAALRCAIAAAAiJ3lxAAAgAElEQVR4nO3dPW7jSgMu6LsJ516IYy

  3. hdu 1513

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1513 思路:正反分别求一次LCS,利用滚动数组对二取余滚动 #include<stdio.h&g ...

  4. md5sum

    [root@NB index]# ls index()().html index()().html index()().html index()().html index()().html [root ...

  5. HTTPS的一些疑问解答

    PHP写的网站怎么用https访问,具体要怎样 这跟用什么语言写的网站没有关系,可以去申请个快速的SSL证书,一年也就几十块. 开启apache server的ssl,自己做个免费的ssl证书或者去申 ...

  6. Delphi如何处理不同类型的文件

    参考:http://www.cnblogs.com/railgunman/articles/1800318.html 程序设计当中,我们时常遇到需要处理文件.目录及驱动器的情况,这里将对如何处理不同类 ...

  7. java-解决业务操可能数据冲突问题

    问题提出,由于业务会出现多人同时操作,或者业务人员反复的操作,因此在业务流程中,需要对业务操作数据进行保护,由于使用数据库锁可能会引起一些难以预料的问题,因此考虑使用内存锁,设计思想:在内存中使用一个 ...

  8. OCJP(1Z0-851) 模拟题分析(五)over

    Exam : 1Z0-851 Java Standard Edition 6 Programmer Certified Professional Exam 以下分析全都是我自己分析或者参考网上的,定有 ...

  9. 安装oracle 10g RAC执行的几个脚本说明

    1,/u01/app/oracle/oraInventory/orainstRoot.sh 脚本 #!/bin/sh if [ -d "/etc" ]; then /etc; fi ...

  10. Could not link against boost_system 解决办法

    Could not link against boost_system 解决办法: 先安装 libboost-all-dev ./configure --with-incompatible-bdb - ...