想象一下以下示例: 应用程序启动两个线程。 Provider类保存并发集合并将数据写入其中。消费者从集合中读取数据。
以下代码是否正确或是否必须添加同步?
public class Application{
public static void main(String...args) throws Exception{
Provider p = new Provider();
new Thread(p).start();
new Thread(new Consumer(p)).start();
// Make sure the example stops after 60 seconds
Thread.sleep(1000*60);
System.exit(0);
}
}
/**The Provider (writes data to concurrent collection)*/
class Provider implements Runnable{
private ConcurrentMap<Integer, String> map
= new ConcurrentHashMap<Integer, String>(20, 0.5f, 1);
public void run(){
Integer i = 1;
while(true){
try {
Thread.sleep(500);
} catch (InterruptedException ignore) {
}
// Synchronization ?
map.put(i, i.toString());
i++;
}
}
public ConcurrentMap<Integer, String> getMap(){
// Synchronization ?
return map;
}
}
/**The Consumer (reads data from concurrent collection)*/
class Consumer implements Runnable{
private Provider provider;
public Consumer(Provider p){
provider = p;
}
public void run(){
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException ignore) {
}
// Synchronization ?
ConcurrentMap<Integer, String> m = provider.getMap();
if(m!=null)
for(String s: m.values())
System.out.print(s);
System.out.println();
}
}
}
答案 0 :(得分:2)
来自ConcurrentHashMap
documentation:
对于集合操作 例如
putAll
和clear
,并发检索可以 反映只插入或删除一些条目。同样的, 迭代器,Spliterators和Enumerations返回反映的元素 在创建时的某个时刻或者自创建之后的哈希表的状态 迭代器/枚举。他们不抛出ConcurrentModificationException
。 但是,迭代器设计为一次只能由一个线程使用。 请记住,聚合状态方法的结果包括 通常是size
,isEmpty
和containsValue
仅当映射未在其他线程中进行并发更新时才有用。 否则,这些方法的结果反映了瞬态 这可能足以用于监测或估计目的,但不是 用于程序控制。
因此,您 需要进行同步,因为您不会获得ConcurrentModificationException
。您想要是否符合您的程序逻辑。
答案 1 :(得分:2)
并发集合的变异是线程安全的,但不保证数据一致性。
备用Collections.synchronized[...]
惯用法与显式同步相结合,可确保线程安全和数据一致性(以性能为代价)。