Java 迭代 ConcurrentHashMap 值线程安全吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3768554/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is iterating ConcurrentHashMap values thread safe?
提问by Palo
In javadoc for ConcurrentHashMapis the following:
在ConcurrentHashMap 的javadoc 中如下:
Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove). Retrievals reflect the results of the most recently completed update operations holding upon their onset. For aggregate operations such as putAll and clear, concurrent retrievals may reflect insertion or removal of only some entries. Similarly, Iterators and Enumerations return elements reflecting the state of the hash table at some point at or since the creation of the iterator/enumeration. They do not throw ConcurrentModificationException. However, iterators are designed to be used by only one thread at a time.
检索操作(包括获取)一般不会阻塞,因此可能与更新操作(包括放置和删除)重叠。检索反映了最近完成的更新操作的结果。对于诸如 putAll 和 clear 之类的聚合操作,并发检索可能仅反映某些条目的插入或删除。类似地,迭代器和枚举返回反映哈希表在迭代器/枚举创建时或创建后的某个时刻的状态的元素。它们不会抛出 ConcurrentModificationException。然而,迭代器被设计为一次只能被一个线程使用。
What does it mean? What happens if I try to iterate the map with two threads at the same time? What happens if I put or remove a value from the map while iterating it?
这是什么意思?如果我尝试同时使用两个线程迭代地图会发生什么?如果我在迭代时从地图中放入或删除一个值会发生什么?
采纳答案by Waldheinz
What does it mean?
这是什么意思?
That means that each iterator you obtain from a ConcurrentHashMap
is designed to be used by a single thread and should not be passed around. This includes the syntactic sugar that the for-each loop provides.
这意味着您从 a 获得的每个迭代器ConcurrentHashMap
都设计为由单个线程使用,不应传递。这包括 for-each 循环提供的语法糖。
What happens if I try to iterate the map with two threads at the same time?
如果我尝试同时使用两个线程迭代地图会发生什么?
It will work as expected if each of the threads uses it's own iterator.
如果每个线程都使用它自己的迭代器,它将按预期工作。
What happens if I put or remove a value from the map while iterating it?
如果我在迭代时从地图中放入或删除一个值会发生什么?
It is guaranteed that things will not break if you do this (that's part of what the "concurrent" in ConcurrentHashMap
means). However, there is no guarantee that one thread will see the changes to the map that the other thread performs (without obtaining a new iterator from the map). The iterator is guaranteed to reflect the state of the map at the time of it's creation. Futher changes may be reflected in the iterator, but they do not have to be.
如果您这样做,可以保证事情不会中断(这是“并发”的ConcurrentHashMap
意思的一部分)。但是,不能保证一个线程会看到另一个线程执行的映射更改(无需从映射中获取新的迭代器)。迭代器保证在地图创建时反映地图的状态。进一步的更改可能会反映在迭代器中,但并非必须如此。
In conclusion, a statement like
总之,像这样的声明
for (Object o : someConcurrentHashMap.entrySet()) {
// ...
}
will be fine (or at least safe) almost every time you see it.
几乎每次看到它都会很好(或至少是安全的)。
回答by nanda
Thismight give you a good insight
这可能会给你一个很好的洞察力
ConcurrentHashMap achieves higher concurrency by slightly relaxing the promises it makes to callers. A retrieval operation will return the value inserted by the most recent completed insert operation, and may also return a value added by an insertion operation that is concurrently in progress (but in no case will it return a nonsense result). Iterators returned by ConcurrentHashMap.iterator() will return each element once at most and will not ever throw ConcurrentModificationException, but may or may not reflect insertions or removals that occurred since the iterator was constructed. No table-wide locking is needed (or even possible) to provide thread-safety when iterating the collection. ConcurrentHashMap may be used as a replacement for synchronizedMap or Hashtable in any application that does not rely on the ability to lock the entire table to prevent updates.
ConcurrentHashMap 通过稍微放松对调用者的承诺来实现更高的并发性。检索操作将返回最近完成的插入操作插入的值,也可能返回由同时进行的插入操作添加的值(但在任何情况下都不会返回无意义的结果)。ConcurrentHashMap.iterator() 返回的迭代器将最多返回每个元素一次并且永远不会抛出 ConcurrentModificationException,但可能会也可能不会反映自迭代器构造以来发生的插入或删除. 在迭代集合时,不需要(甚至可能)不需要全表锁定来提供线程安全。ConcurrentHashMap 可以在任何不依赖于锁定整个表以防止更新的能力的应用程序中用作 synchronizedMap 或 Hashtable 的替代品。
Regarding this:
关于这一点:
However, iterators are designed to be used by only one thread at a time.
然而,迭代器被设计为一次只能被一个线程使用。
It means, while using iterators produced by ConcurrentHashMap in two threads are safe, it may cause an unexpected result in the application.
这意味着,虽然在两个线程中使用 ConcurrentHashMap 产生的迭代器是安全的,但它可能会导致应用程序出现意外结果。
回答by Tuure Laurinolli
It means that you should not share an iterator object among multiple threads. Creating multiple iterators and using them concurrently in separate threads is fine.
这意味着您不应该在多个线程之间共享迭代器对象。创建多个迭代器并在单独的线程中同时使用它们很好。
回答by Boris Pavlovi?
You may use this class to test two accessing threads and one mutating the shared instance of ConcurrentHashMap
:
你可以使用这个类来测试两个访问线程和一个改变 的共享实例ConcurrentHashMap
:
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ConcurrentMapIteration
{
private final Map<String, String> map = new ConcurrentHashMap<String, String>();
private final static int MAP_SIZE = 100000;
public static void main(String[] args)
{
new ConcurrentMapIteration().run();
}
public ConcurrentMapIteration()
{
for (int i = 0; i < MAP_SIZE; i++)
{
map.put("key" + i, UUID.randomUUID().toString());
}
}
private final ExecutorService executor = Executors.newCachedThreadPool();
private final class Accessor implements Runnable
{
private final Map<String, String> map;
public Accessor(Map<String, String> map)
{
this.map = map;
}
@Override
public void run()
{
for (Map.Entry<String, String> entry : this.map.entrySet())
{
System.out.println(
Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']'
);
}
}
}
private final class Mutator implements Runnable
{
private final Map<String, String> map;
private final Random random = new Random();
public Mutator(Map<String, String> map)
{
this.map = map;
}
@Override
public void run()
{
for (int i = 0; i < 100; i++)
{
this.map.remove("key" + random.nextInt(MAP_SIZE));
this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString());
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
private void run()
{
Accessor a1 = new Accessor(this.map);
Accessor a2 = new Accessor(this.map);
Mutator m = new Mutator(this.map);
executor.execute(a1);
executor.execute(m);
executor.execute(a2);
}
}
No exception will be thrown.
不会抛出任何异常。
Sharing the same iterator between accessor threads can lead to deadlock:
在访问线程之间共享同一个迭代器会导致死锁:
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ConcurrentMapIteration
{
private final Map<String, String> map = new ConcurrentHashMap<String, String>();
private final Iterator<Map.Entry<String, String>> iterator;
private final static int MAP_SIZE = 100000;
public static void main(String[] args)
{
new ConcurrentMapIteration().run();
}
public ConcurrentMapIteration()
{
for (int i = 0; i < MAP_SIZE; i++)
{
map.put("key" + i, UUID.randomUUID().toString());
}
this.iterator = this.map.entrySet().iterator();
}
private final ExecutorService executor = Executors.newCachedThreadPool();
private final class Accessor implements Runnable
{
private final Iterator<Map.Entry<String, String>> iterator;
public Accessor(Iterator<Map.Entry<String, String>> iterator)
{
this.iterator = iterator;
}
@Override
public void run()
{
while(iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
try
{
String st = Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']';
} catch (Exception e)
{
e.printStackTrace();
}
}
}
}
private final class Mutator implements Runnable
{
private final Map<String, String> map;
private final Random random = new Random();
public Mutator(Map<String, String> map)
{
this.map = map;
}
@Override
public void run()
{
for (int i = 0; i < 100; i++)
{
this.map.remove("key" + random.nextInt(MAP_SIZE));
this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString());
}
}
}
private void run()
{
Accessor a1 = new Accessor(this.iterator);
Accessor a2 = new Accessor(this.iterator);
Mutator m = new Mutator(this.map);
executor.execute(a1);
executor.execute(m);
executor.execute(a2);
}
}
As soon as you start sharing the same Iterator<Map.Entry<String, String>>
among accessor and mutator threads java.lang.IllegalStateException
s will start popping up.
一旦您开始Iterator<Map.Entry<String, String>>
在访问器和修改器线程之间共享相同的内容,java.lang.IllegalStateException
就会开始弹出。
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ConcurrentMapIteration
{
private final Map<String, String> map = new ConcurrentHashMap<String, String>();
private final Iterator<Map.Entry<String, String>> iterator;
private final static int MAP_SIZE = 100000;
public static void main(String[] args)
{
new ConcurrentMapIteration().run();
}
public ConcurrentMapIteration()
{
for (int i = 0; i < MAP_SIZE; i++)
{
map.put("key" + i, UUID.randomUUID().toString());
}
this.iterator = this.map.entrySet().iterator();
}
private final ExecutorService executor = Executors.newCachedThreadPool();
private final class Accessor implements Runnable
{
private final Iterator<Map.Entry<String, String>> iterator;
public Accessor(Iterator<Map.Entry<String, String>> iterator)
{
this.iterator = iterator;
}
@Override
public void run()
{
while (iterator.hasNext())
{
Map.Entry<String, String> entry = iterator.next();
try
{
String st =
Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']';
} catch (Exception e)
{
e.printStackTrace();
}
}
}
}
private final class Mutator implements Runnable
{
private final Random random = new Random();
private final Iterator<Map.Entry<String, String>> iterator;
private final Map<String, String> map;
public Mutator(Map<String, String> map, Iterator<Map.Entry<String, String>> iterator)
{
this.map = map;
this.iterator = iterator;
}
@Override
public void run()
{
while (iterator.hasNext())
{
try
{
iterator.remove();
this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString());
} catch (Exception ex)
{
ex.printStackTrace();
}
}
}
}
private void run()
{
Accessor a1 = new Accessor(this.iterator);
Accessor a2 = new Accessor(this.iterator);
Mutator m = new Mutator(map, this.iterator);
executor.execute(a1);
executor.execute(m);
executor.execute(a2);
}
}
回答by Stephen C
What does it mean?
这是什么意思?
It means that you should not try to use the same iterator in two threads. If you have two threads that need to iterate over the keys, values or entries, then they each should create and use their own iterators.
这意味着您不应该尝试在两个线程中使用相同的迭代器。如果您有两个线程需要迭代键、值或条目,那么它们每个都应该创建并使用自己的迭代器。
What happens if I try to iterate the map with two threads at the same time?
如果我尝试同时使用两个线程迭代地图会发生什么?
It is not entirely clear what would happen if you broke this rule. You could just get confusing behavior, in the same way that you do if (for example) two threads try to read from standard input without synchronizing. You could also get non-thread-safe behavior.
如果您违反此规则,会发生什么并不完全清楚。您可能会遇到令人困惑的行为,就像(例如)两个线程尝试在不同步的情况下从标准输入读取时一样。您还可以获得非线程安全的行为。
But if the two threads used different iterators, you should be fine.
但是如果两个线程使用不同的迭代器,你应该没问题。
What happens if I put or remove a value from the map while iterating it?
如果我在迭代时从地图中放入或删除一个值会发生什么?
That's a separate issue, but the javadoc section that you quoted adequately answers it. Basically, the iterators are thread-safe, but it is not definedwhether you will see the effects of any concurrent insertions, updates or deletions reflected in the sequence of objects returned by the iterator. In practice, it probably depends on where in the map the updates occur.
这是一个单独的问题,但您引用的 javadoc 部分充分回答了它。基本上,迭代器是线程安全的,但没有定义是否会看到迭代器返回的对象序列中反映的任何并发插入、更新或删除的影响。实际上,这可能取决于更新发生在地图中的哪个位置。