在随后的博文中我会继续分析并发包源码,在这里,得分别谈谈容器类和迭代器及其源码,虽然很突兀,但我认为这对于学习Java并发很重要;

ConcurrentModificationException:

  JavaAPI中的解释:当不允许这样的修改时,可以通过检测到对象的并发修改的方法来抛出此异常。一个线程通常不允许修改集合,而另一个线程正在遍历它。 一般来说,在这种情况下,迭代的结果是未定义的。 某些迭代器实现(包括由JRE提供的所有通用集合实现的实现)可能会选择在检测到此行为时抛出此异常。 这样做的迭代器被称为"及时失败"迭代器,当他们发现容器在迭代时被修改时,就会报异常;它称不上时一种处理机制,而是一种预防机制,只能作为并发问题的预警指示器.

迭代器与容器:

  Vector这个"古老"的容器类相信大家很熟悉了,他是线程安全的没错,我们利用elements()方法遍历,不会出现线程安全的问题:

 /**
* JDK1.8源码
*/ /**
* Returns an enumeration of the components of this vector. The
* returned {@code Enumeration} object will generate all items in
* this vector. The first item generated is the item at index {@code 0},
* then the item at index {@code 1}, and so on.
*
* @return an enumeration of the components of this vector
* @see Iterator
*/
public Enumeration<E> elements() {
return new Enumeration<E>() {
int count = 0; public boolean hasMoreElements() {
return count < elementCount;
} public E nextElement() {
synchronized (Vector.this) {
if (count < elementCount) {
return elementData(count++);
}
}
throw new NoSuchElementException("Vector Enumeration");
}
};
}

JDK1.8源码: AbstractCollection (been extends by Vector)

  但当我们利用foreach进行迭代时,底层自动调用了iterator(),若我们不锁住容器,可能会报ConcurrentModificationException

 /**
* Returns an iterator over the elements in this list in proper sequence.
*
* <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @return an iterator over the elements in this list in proper sequence
*/
public Iterator<E> iterator() {
return new Itr();
//JDK1.8写成了一个内部类的形式返回迭代器
} /**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount; Itr() {} public boolean hasNext() {
return cursor != size;
} //观察下面的代码可以发现,每次迭代一次都要检查容器长度是否改变
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
} public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
} @Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

JDK1.8源码: Vector

  查看代码时可以发现,iterator()的内部类中提供的next,remove等方法都进行了迭代操作,这样的迭代被称为"隐藏迭代器";

  部分容器类的toString()方法会在调用StringBuilder的append()同时迭代容器,例如继承了AbstractSet的HashSet类,而AbstractSet又继承于AbstractCollection:

//  String conversion

    /**
* Returns a string representation of this collection. The string
* representation consists of a list of the collection's elements in the
* order they are returned by its iterator, enclosed in square brackets
* (<tt>"[]"</tt>). Adjacent elements are separated by the characters
* <tt>", "</tt> (comma and space). Elements are converted to strings as
* by {@link String#valueOf(Object)}.
*
* @return a string representation of this collection
*/
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]"; StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}

JDK1.8源码: AbstractCollection

  隐藏的迭代器普遍存在,甚至出现在hashCode,equals,contains,remove等方法中,此处不再赘述;

解决办法:

  考虑到并发安全性,我们不得不对容器类单独加锁,但同时我们又不希望加锁,那样太损耗性能了,还存在线程饥饿和死锁的风险,极大降低吞吐量和CPU利用率;

  作为有限的替代,我们可以考虑"克隆"容器,并在将其副本封闭起来进行迭代,避免了抛出ConcurrentModificationException,但在克隆过程中仍然要对容器加锁;显然,克隆容器存在系统开销,我们在选择这种方案时必须考虑的因素有:容器大小,每个元素上执行的工作,迭代操作相对于其它操作的调用频率,以及相应时间和系统吞吐率的需求,具体应用详见CopyOnWriteArrayList类.

参考材料:

  Java并发编程实战

最新文章

  1. 使用Visual Studio SDK制作GLSL词法着色插件
  2. 自定义项目脚手架- Maven Archetypes
  3. 二叉搜索树的实现及指针问题的一点思考(C++)
  4. javascript 如何访问 action或者controller 传给 jsp 页面的值
  5. C#判断文件是复制还是剪切
  6. 黄聪:WordPress动作钩子函数add_action()、do_action()源码解析
  7. Android常见工具类封装
  8. Jquery 工具类函数
  9. js变量数组
  10. lesson - 5 Linux用户和组管理
  11. 简单 php 代码跟踪调试实现
  12. 安全测试之Top 10 漏洞的分析
  13. .NET领域最为流行的IOC框架之一Autofac WebAPI2使用Autofac实现IOC属性注入完美解决方案 AutoFac容器初步
  14. 3.Liunx网络管理命令
  15. 关于Cell中的各种值的类型判断
  16. 设计模式 笔记 适配器模式 Adapter
  17. marquee标签详解
  18. CSS垂直导航栏
  19. 在ASP.NET应用程序中使用身份模拟(Impersonation)
  20. VM CentOS7 网络配置问题汇总

热门文章

  1. Servlet编程实例 续4
  2. xgene:肿瘤相关基因 KRAS,,BRAF,,通路PI3K-AKT
  3. SDK和JDK的区别
  4. SQL Server中通过设置非聚集索引(Non-Clustered index)来达到性能优化的目的
  5. android build system resource links
  6. C# 写 LeetCode easy #27 Remove Element
  7. Dijkstra 路径规划 C#
  8. SqlServer2012-创建表、删除表 增加字段 删除字段操作
  9. HTML 表单 / HTML5 表单元素datalist
  10. 浮点数与快速log2