如题,最近项目里有个模块我做了异步处理方面的事情,在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人的乐园

最新文章

  1. Linux下查看软件的安装路径
  2. L2TP协议
  3. 关于malloc的一些想法
  4. css动画与js动画的区别
  5. [C#解惑] #1 在构造函数内调用虚方法
  6. 3. Android程序生成步骤
  7. ACM 字符串替换
  8. Java入门到精通——工具篇之Maven概述
  9. DES加密算法—实现(C语言)
  10. 干货 | PHP就该这么学!
  11. 完成登录功能,用session记住用户名
  12. CAFE: a computational tool for the study of gene family evolution
  13. CSS常见布局
  14. M1m2分析报告
  15. awk详解2
  16. 如何在github上创建仓库,并将本地的文件上传到对应的远程仓库
  17. JKS和PFX文件相互转换方法
  18. hive介绍、安装配置、表操作基础知识适合小白学习
  19. ubuntu16.04 关闭防火墙的方法
  20. Java加密代码 转换成Net版

热门文章

  1. [Android Pro] Android 性能分析工具dumpsys的使用
  2. 二叉树计数(codevs 3112)
  3. XMPP框架下微信项目总结(4)重新连接服务器
  4. 四、优化及调试--网站优化--Yahoo军规中
  5. I2C学习
  6. ASP.NET MVC中的Global.asax文件
  7. Delphi中record和packed record的区别
  8. SQL语法中的JOIN类型
  9. [LeetCode] Isomorphic Strings
  10. Sql Server 索引之唯一索引和筛选索引