我理解像Hashtable这样的集合是同步的,但是有人可以向我解释 它是如何工作的,以及在什么时候访问仅限于并发调用?例如,假设我使用了一些像这样的迭代器:
Hashtable<Integer,Integer> map = new Hashtable<Integer,Integer>();
void dosomething1(){
for (Iterator<Map.Entry<Integer,Integer>> i = map.entrySet().iterator(); i.hasNext();){
// do something
}
}
void dosomething2(){
for (Iterator<Map.Entry<Integer,Integer>> i = map.entrySet().iterator(); i.hasNext();){
// do something
// and remove it
i.remove();
}
}
void putsomething(int a, int b){
map.put(a,b);
}
void removesomething(int a){
map.remove(a);
}
var clear(){
map = new Hashtable<Integer,Integer>();
}
有人可以解释我是否有任何陷阱让我从不同的线程中随机调用这些函数?特别是迭代器如何进行同步,特别是在使用entrySet()时,它似乎也需要同步?如果在其中一个循环正在进行时调用clear()会发生什么?如果removedomething()删除了dosomething1()中并发循环尚未处理的项,该怎么办?
感谢您的帮助!
答案 0 :(得分:39)
即使您正在使用其中一个同步包装器(Collections.synchronizedMap(...)
),对Java中集合的迭代也不是线程安全的:
用户必须手动同步返回的内容 迭代任何集合视图时映射:
Map m = Collections.synchronizedMap(new HashMap()); ... Set s = m.keySet(); // Needn't be in synchronized block ... synchronized(m) { // Synchronizing on m, not s! Iterator i = s.iterator(); // Must be in synchronized block while (i.hasNext()) foo(i.next()); }
Java Collection Framework docs
对同步集合的其他调用是安全的,因为包装类用synchronized
块包围它们,这些块使用包装器集合作为它们的监视器:
public int size() {
synchronized( this ) {
return collection.size();
}
}
collection
是原始集合。这适用于集合/映射所公开的所有方法,迭代内容除外。
映射的密钥集以同样的方式进行同步:同步包装器根本不返回原始密钥集。相反,它返回集合的原始密钥集的特殊同步包装。这同样适用于条目集和值集。
答案 1 :(得分:0)
我了解像Hashtable这样的集合是同步的
HashTable的条目集使用SynchronizedSet,它是SynchronizedCollection的一种。
如果在使用迭代器的同时修改了是否同步了任何集合,则迭代器将引发ConcurrentModificationException。
迭代器是一个作用于集合的Object,在构造期间会被赋予该集合的状态。这样,您就可以决定何时查看集合中的下一个项目(如果有)。您必须在知道不会被修改的集合上使用迭代器,或者仅计划使用迭代器进行修改。
引发ConcurrentModificationException的原因是由于迭代器对集合的当前修改计数进行检查,如果它与期望值不匹配,则会引发异常。每次添加或删除某些内容时,所有集合都会增加一个修改计数变量。
尤其是迭代器如何进行同步,尤其是在使用entrySet()时
因此,迭代器不会进行同步,并且当您希望集合被其他线程(或迭代器外部的当前线程)修改时,使用它是不安全的。
但是, SynchronizedCollection 确实提供了一种同步浏览集合的方法。它的 forEach方法的实现已同步。
public void forEach(Consumer<? super E> consumer)
请记住,forEach使用了增强的for循环,该循环在内部使用迭代器。这意味着forEach仅用于查看集合的内容,而不用于在浏览过程中对其进行修改。否则将引发ConcurrentModificationException。
有人可以向我解释它的工作原理,以及在什么时候只能进行并发呼叫
SynchronizedCollection导致线程要使用(添加,删除,forEach)之类的同步方法来轮流访问该集合。
通过引入类似于以下代码所示的同步块来工作:
public boolean add(Object o) {
synchronized(this) {
super.add(o);
}
}
除了以下方法外,围绕您可以对集合执行的所有操作引入了同步块:
iterator(), spliterator(), stream(), parallelStream()