Pal*_*alo 144 java concurrency multithreading thread-safety concurrenthashmap
在ConcurrentHashMap的 javadoc中有以下内容:
检索操作(包括get)通常不会阻塞,因此可能与更新操作(包括put和remove)重叠.检索反映了最近完成的更新操作的结果.对于诸如putAll和clear之类的聚合操作,并发检索可能反映仅插入或删除某些条目.类似地,Iterators和Enumerations在迭代器/枚举的创建时或之后的某个时刻返回反映哈希表状态的元素.它们不会抛出ConcurrentModificationException.但是,迭代器设计为一次只能由一个线程使用.
这是什么意思?如果我尝试同时使用两个线程迭代地图会发生什么?如果我在迭代时从地图中添加或删除值会发生什么?
Wal*_*inz 181
这是什么意思?
这意味着您从a获得的每个迭代器都ConcurrentHashMap被设计为由单个线程使用,不应传递.这包括for-each循环提供的语法糖.
如果我尝试同时使用两个线程迭代地图会发生什么?
如果每个线程都使用它自己的迭代器,它将按预期工作.
如果我在迭代时从地图中添加或删除值会发生什么?
如果你这样做,那么事情就不会破坏(这就是"并发"的ConcurrentHashMap意思).但是,无法保证一个线程会看到另一个线程执行的映射更改(无需从映射中获取新的迭代器).迭代器保证在创建时反映地图的状态.未来的变化可能会反映在迭代器中,但它们并非必须如此.
总之,一个声明就像
for (Object o : someConcurrentHashMap.entrySet()) {
// ...
}
Run Code Online (Sandbox Code Playgroud)
几乎每次看到它都会很好(或至少是安全的).
Bor*_*vić 18
您可以使用此类来测试两个访问线程,并使用一个变更共享实例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);
}
}
Run Code Online (Sandbox Code Playgroud)
不会抛出异常.
在访问者线程之间共享相同的迭代器可能会导致死锁:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
一旦你开始Iterator<Map.Entry<String, String>>在访问者和mutator线程之间共享相同的内容,java.lang.IllegalStateExceptions就会开始弹出.
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);
}
}
Run Code Online (Sandbox Code Playgroud)
这可能会给你一个很好的洞察力
ConcurrentHashMap通过稍微放松它对调用者的承诺来实现更高的并发性.检索操作将返回最近完成的插入操作所插入的值,并且还可以返回由同时进行的插入操作添加的值(但在任何情况下都不会返回无意义的结果).ConcurrentHashMap.iterator()返回的迭代器将最多返回一次元素,并且不会抛出ConcurrentModificationException,但可能会或可能不会反映自迭代器构造以来发生的插入或删除.在迭代集合时,不需要表格范围的锁定(甚至可能)来提供线程安全性.ConcurrentHashMap可以在任何不依赖于锁定整个表以防止更新的应用程序中用作synchronizedMap或Hashtable的替代.
关于这个:
但是,迭代器设计为一次只能由一个线程使用.
这意味着,虽然在两个线程中使用ConcurrentHashMap生成的迭代器是安全的,但它可能会在应用程序中导致意外结果.
这是什么意思?
这意味着您不应该尝试在两个线程中使用相同的迭代器。如果您有两个线程需要迭代键、值或条目,那么它们每个都应该创建并使用自己的迭代器。
如果我尝试同时使用两个线程迭代映射会发生什么?
目前还不完全清楚如果违反这条规则会发生什么。您可能会得到令人困惑的行为,就像(例如)两个线程尝试从标准输入读取而不同步一样。您还可能会得到非线程安全的行为。
但如果两个线程使用不同的迭代器,那么应该没问题。
如果我在迭代地图时添加或删除地图中的值,会发生什么情况?
如果两个线程使用相同的迭代器:请参见上文。您很容易出现令人困惑且可能是非线程安全的行为。
如果线程使用不同的迭代器,您引用的 javadoc 部分足以回答这个问题。基本上,没有定义一个线程/迭代器是否会看到另一线程/迭代器进行的任何并发插入、更新或删除的效果。但是,插入/更新/删除将根据映射的并发属性进行。