Java并发(五):并发,迭代器和容器
在随后的博文中我会继续分析并发包源码,在这里,得分别谈谈容器类和迭代器及其源码,虽然很突兀,但我认为这对于学习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并发编程实战
Java并发(五):并发,迭代器和容器的更多相关文章
- java 并发(五)---AbstractQueuedSynchronizer(5)
问题 : ArrayBlockQueue 和 LinkedBlockQueue 的区别 两者的实现又是怎么样的 应用场景 BlockingQueue 概述 blockingQueue 是个接口,从名字 ...
- java 并发(五)---AbstractQueuedSynchronizer
文章部分图片和代码来自参考文章. LockSupport 和 CLH 和 ConditionObject 阅读源码首先看一下注解 ,知道了大概的意思后,再进行分析.注释一开始就进行了概括.AQS的实现 ...
- JAVA多线程和并发基础面试问答(转载)
JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...
- [转] JAVA多线程和并发基础面试问答
JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...
- JAVA多线程和并发基础面试问答
转载: JAVA多线程和并发基础面试问答 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对 ...
- 【多线程】JAVA多线程和并发基础面试问答(转载)
JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...
- java处理高并发高负载类网站的优化方法
java处理高并发高负载类网站中数据库的设计方法(java教程,java处理大量数据,java高负载数据) 一:高并发高负载类网站关注点之数据库 没错,首先是数据库,这是大多数应用所面临的首个SPOF ...
- (转)JAVA多线程和并发基础面试问答
JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...
- Java编程思想 - 并发
前言 Q: 为什么学习并发? A: 到目前为止,你学到的都是有关顺序编程的知识,即程序中的所有事物在任意时刻都只能执行一个步骤. A: 编程问题中相当大的一部分都可以通过使用顺序编程来解决,然而,对于 ...
随机推荐
- mac上如何查看gif
今天生成了一个gif,结果用mac自带的图片预览功能打开,图片被切成一张一张的,不是动图效果了.原以为还得下第三方看图软件,后来百度下发现mac本身也可以打开. 方法一: 鼠标右击图片,选择“快速查看 ...
- 树莓派 Learning 002 装机后的必要操作 --- 00 修改键盘布局
树莓派 装机后的必要操作 - 修改键盘布局 我的树莓派型号:Raspberry Pi 2 Model B V1.1 装机系统:NOOBS v1.9.2 上网查,发现树莓派的键盘布局不对,树莓派(ras ...
- ssh远程转发使远程主机在所有ip上监听
起因:突然一夜之间电信扰拨号ip全变内网地址了,这样即使用了动态域名,绑定的也不是本机ip,外部无法访问了.虽然打电话找电信反映说是可以改回来,但必须先解决眼前的问题,访问内网服务器上的svn仓库. ...
- How to install Samba server on Ubuntu 12.04
Part 1: Configuring anonymous share with samba server To install the samba package,enter the followi ...
- .net core webapi +ddd(领域驱动)+nlog配置+swagger配置 学习笔记(2)
DDD领域驱动模型设计 什么是DDD 软件开发不是一蹴而就的事情,我们不可能在不了解产品(或行业领域)的前提下进行软件开发,在开发前,通常需要进行大量的业务知识梳理,而后到达软件设计的层面,最后才是开 ...
- 浅谈JavaScript--闭包
闭包的概念 由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数". 变量的作用域 要理解闭包,首先必须理解J ...
- 【NOIP模拟赛】收银员(一道差分约束好题)
/* s[]表示最优方案的序列中的前缀和,那么s[23]就是最优方案 由题意我们可以列出这样一些式子: s[i]+s[23]-s[16+i]>=a[i] (i-8<0) s[i]-s[i- ...
- VS2010 不显示 最近使用的项目 解决办法(转)
昨天重装了VS2010,然后开了项目看了下今天早上再打开发现起始页近使用项目列表是空白的,每次打开项目都要去到指定目录去找解决方案才能打开,感觉很麻烦,在网上找了下解决方案,解决步骤下:菜单 —— 运 ...
- C#网络编程学习(6)---序列化和反序列化
1.什么是序列化和反序列化 当客户端和服务器进行远程连接时,互相可以发送各种类型的数据.但都要先把这些对象转换为字节序列,才能在网络上进行传输. 序列化:就是发送方 把对象转换为字节序列的过程. 反序 ...
- Spring ThreadPoolTaskExecutor队列满的异常处理
<!-- 配置线程池 --> <bean id="threadPool" class="org.springframework.scheduling.c ...