此图是verctor容器产生并发的一个说明
虽然加锁可以防止迭代器抛出 concurentModicationException,但你必须记住对所有共享容器进行迭代的地方
都需要进行加锁。
经常在迭代集合元素时,会想对集合做修改(add/remove)操作,类似下面这段代码:
[java] view plaincopy
for (Iterator<Integer> it = list.iterator(); it.hasNext(); ) {
Integer val = it.next();
if (val == 5) {
list.remove(val);
}
}
运行这段代码,会抛出异常java.util.ConcurrentModificationException。
【解惑】
(以ArrayList来讲解)在ArrayList中,它的修改操作(add/remove)都会对modCount这个字段+1,modCount可以看作一个版本号,每次集合中的元素被修改后,都会+1(即使溢出)。接下来再看看AbsrtactList中iteraor方法
[java] view plaincopy
public Iterator<E> iterator() {
return new Itr();
}
它返回一个内部类,这个类实现了iterator接口,代码如下:
[java] view plaincopy
private class Itr implements Iterator<E> {
int cursor = 0;
int lastRet = -1;
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
// 修改expectedModCount 的值
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
在内部类Itr中,有一个字段expectedModCount ,初始化时等于modCount,即当我们调用list.iterator()返回迭代器时,该字段被初始化为等于modCount。在类Itr中next/remove方法都有调用checkForComodification()方法,在该方法中检测modCount == expectedModCount,如果不相当则抛出异常ConcurrentModificationException。
前面说过,在集合的修改操作(add/remove)中,都对modCount进行了+1。
在看看刚开始提出的那段代码,在迭代过程中,执行list.remove(val),使得modCount+1,当下一次循环时,执行 it.next(),checkForComodification方法发现modCount != expectedModCount,则抛出异常。
【解决办法】
如果想要在迭代的过程中,执行删除元素操作怎么办?
再来看看内部类Itr的remove()方法,在删除元素后,有这么一句expectedModCount = modCount,同步修改expectedModCount 的值。所以,如果需要在使用迭代器迭代时,删除元素,可以使用迭代器提供的remove方法。对于add操作,则在整个迭代器迭代过程中是不允许的。 其他集合(Map/Set)使用迭代器迭代也是一样。
并发容器:
1:同步容器是将所有对容器状态的访问都串行化,以实现他们的线程安全性,这种方法的代价是严重降低并发性,当多个线程竞争容器的锁时,吞吐量将严重降低
并发容器是针对多线程并发访问设计的
2:concurrentHashMap://用来代替同步,且基于散列的HashMap
3:CopyOnWriteArrayList用于遍历操作为主要操作的情况下代替同步的List,
concurrentHashMap增加了一些常见的复合操作。若没有则添加,替换,以及有条件删除
queue和blockingQueue//队列
queue上的操作不会阻塞,如果队列为空那么获取那么获取元素的操作将返回空值,事实上正是通过linkedList来实现queue,但还需要一个queue类因为其能去掉linkedList的随机访问
blockQueue增加了阻塞的操作,如果队列为空那么获取元素的操作将阻塞,直到出现一个可用的元素,如果队列已满,那么插入操作将会阻塞直到队列中出现可用的空间,
生产者和消费者模式就是典型的使用场景。
Java 6也引用了
concurrentskipList concurrentSkipMap 分别作为sortMap和sortSet的替代品
例如用synchronizedMap包装TreeMap和treeSet
concurrentHashmap也是一个基于散列的hashMap但是其内部 使用粒度更细的加锁机制,即加锁机制就是使用分段锁