Java 使用ConcurrentHashMap,何时需要同步?
我有一个ConcurrentHashMap,我在其中执行以下操作:Java 使用ConcurrentHashMap,何时需要同步?,java,concurrency,concurrenthashmap,Java,Concurrency,Concurrenthashmap,我有一个ConcurrentHashMap,我在其中执行以下操作: sequences = new ConcurrentHashMap<Class<?>, AtomicLong>(); if(!sequences.containsKey(table)) { synchronized (sequences) { if(!sequences.containsKey(table)) initializeHashMapKeyVal
sequences = new ConcurrentHashMap<Class<?>, AtomicLong>();
if(!sequences.containsKey(table)) {
synchronized (sequences) {
if(!sequences.containsKey(table))
initializeHashMapKeyValue(table);
}
}
检查synschronized块内部,以便其他线程不会初始化相同的hashmap值
也许检查是必要的,而我做错了?我这样做似乎有点傻,但我认为这是必要的。你不能用ConcurrentHashMap获得独占锁。在这种情况下,最好使用同步HashMap
如果对象不在ConcurrentHashMap中,那么已经有了一个原子的方法放在ConcurrentHashMap中
putIfAbsent
ConcurrentHashMap上的所有操作都是线程安全的,但线程安全的操作是不可组合的。你试图让原子成为一对操作:检查地图上的东西,如果它不在那里,就把东西放在那里(我猜)。因此,您的问题的答案是“是”,您需要再次检查,您的代码看起来正常。我知道您在那里做了什么;-)问题是你自己看到了吗
首先,你使用了一种叫做“双重检查锁定模式”的东西。其中,快速路径(第一个包含)在满足条件时不需要同步,而慢速路径由于执行复杂操作而必须同步。你的操作包括检查地图中是否有东西,然后把东西放在那里/初始化它。因此,ConcurrentHashMap对于单个操作来说是线程安全的并不重要,因为您执行两个简单的操作,必须将其视为一个单元。因此,是的,此同步块是正确的,实际上它可以通过任何其他方法进行同步,例如,此
您应该使用ConcurrentMap
的方法
ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong> ();
public long addTo(String key, long value) {
// The final value it became.
long result = value;
// Make a new one to put in the map.
AtomicLong newValue = new AtomicLong(value);
// Insert my new one or get me the old one.
AtomicLong oldValue = map.putIfAbsent(key, newValue);
// Was it already there? Note the deliberate use of '!='.
if ( oldValue != newValue ) {
// Update it.
result = oldValue.addAndGet(value);
}
return result;
}
在Java 8中,我们可以避免不必要地创建一个AtomicLong
:
public long addTo8(String key, long value) {
return map.computeIfAbsent(key, k -> new AtomicLong()).addAndGet(value);
}
在Java 8中,您应该能够使用
.computeIfAbsent
:
sequences.computeIfAbsent(table, k -> initializeHashMapKeyValue(k));
创建一个名为dictionary.txt的文件,其中包含以下内容:
a
as
an
b
bat
ball
我们有:
以“a”开头的字数:3
以“b”开头的字数:3
总字数:6
现在执行以下程序:java WordCount test_dictionary.txt 10
public class WordCount {
String fileName;
public WordCount(String fileName) {
this.fileName = fileName;
}
public void process() throws Exception {
long start = Instant.now().toEpochMilli();
LongAdder totalWords = new LongAdder();
//Map<Character, LongAdder> wordCounts = Collections.synchronizedMap(new HashMap<Character, LongAdder>());
ConcurrentHashMap<Character, LongAdder> wordCounts = new ConcurrentHashMap<Character, LongAdder>();
Files.readAllLines(Paths.get(fileName))
.parallelStream()
.map(line -> line.split("\\s+"))
.flatMap(Arrays::stream)
.parallel()
.map(String::toLowerCase)
.forEach(word -> {
totalWords.increment();
char c = word.charAt(0);
if (!wordCounts.containsKey(c)) {
wordCounts.put(c, new LongAdder());
}
wordCounts.get(c).increment();
});
System.out.println(wordCounts);
System.out.println("Total word count: " + totalWords);
long end = Instant.now().toEpochMilli();
System.out.println(String.format("Completed in %d milliseconds", (end - start)));
}
public static void main(String[] args) throws Exception {
for (int r = 0; r < Integer.parseInt(args[1]); r++) {
new WordCount(args[0]).process();
}
}
公共类字数{
字符串文件名;
公共字数(字符串文件名){
this.fileName=文件名;
}
public void进程()引发异常{
长时间开始=瞬间.now().toEpochMilli();
LongAdder totalWords=新的LongAdder();
//Map wordCounts=Collections.synchronizedMap(新HashMap());
ConcurrentHashMap字计数=新的ConcurrentHashMap();
Files.readAllLines(path.get(fileName))
.parallelStream()
.map(直线->直线分割(\\s+))
.flatMap(数组::流)
.parallel()
.map(字符串::toLowerCase)
.forEach(word->{
totalWords.increment();
字符c=字字符(0);
如果(!wordCounts.containsKey(c)){
put(c,新的LongAdder());
}
get(c.increment();
});
System.out.println(字数);
System.out.println(“总字数:“+totalWords”);
long end=Instant.now().toEpochMilli();
System.out.println(String.format(“在%d毫秒内完成”,“结束-开始”);
}
公共静态void main(字符串[]args)引发异常{
for(int r=0;r
}
您将看到计数变化如下所示:
{a=2,b=3}
总字数:6
在77毫秒内完成
{a=3,b=3}
总字数:6
现在注释掉第13行的ConcurrentHashMap,取消注释上面的行,然后再次运行程序
您将看到确定性计数。当您想要实现ConcurrentHashMap时,此实例上同步对象锁的要求是什么。无法在映射上获得锁的事实并不妨碍您同步对映射的访问。同意。。。但它是否增加了任何价值。事实上,您会有一个错误的印象。ConcurrentMap将实现更好的并发性-因此,如果
sequences.containsKey(table)
通常是正确的,如果性能是一个问题,使用并发映射仍然是有意义的。这是一个有效的建议。在大多数情况下,避免进行过多的查找。您可以跳过额外检查的一种情况是,您不介意对相同的表值多次初始化hashmapkeyvalue(table)
。这可能是因为initializeHashMapKeyValue
没有副作用,而且价格相当便宜。如果initializeHashMapKeyValue
花费大量时间或有副作用,那么使用OP模式可能是有意义的。@assylias-如果副作用不可避免,我想我会同意,但有很多方法可以解决使用factory methods等方法避免/回避副作用。我会尽可能使用此模式,因为它保证不存在竞争条件,而双重检查锁定模式以存在非常微妙的问题而闻名。双重检查锁定可能存在问题,但在这个特定示例中不存在。请注意,我并不是说您的方法不合适-我只是说OP的方法在某些情况下可能更好。如果(oldValue!=null)返回oldValue.addAndGet(value),则需要;否则返回值
或等效值,因为当确实不存在时,oldValue
将为null
。这个混乱的api不返回newValue
,这也阻止了尼斯的简化版本。
a
as
an
b
bat
ball
public class WordCount {
String fileName;
public WordCount(String fileName) {
this.fileName = fileName;
}
public void process() throws Exception {
long start = Instant.now().toEpochMilli();
LongAdder totalWords = new LongAdder();
//Map<Character, LongAdder> wordCounts = Collections.synchronizedMap(new HashMap<Character, LongAdder>());
ConcurrentHashMap<Character, LongAdder> wordCounts = new ConcurrentHashMap<Character, LongAdder>();
Files.readAllLines(Paths.get(fileName))
.parallelStream()
.map(line -> line.split("\\s+"))
.flatMap(Arrays::stream)
.parallel()
.map(String::toLowerCase)
.forEach(word -> {
totalWords.increment();
char c = word.charAt(0);
if (!wordCounts.containsKey(c)) {
wordCounts.put(c, new LongAdder());
}
wordCounts.get(c).increment();
});
System.out.println(wordCounts);
System.out.println("Total word count: " + totalWords);
long end = Instant.now().toEpochMilli();
System.out.println(String.format("Completed in %d milliseconds", (end - start)));
}
public static void main(String[] args) throws Exception {
for (int r = 0; r < Integer.parseInt(args[1]); r++) {
new WordCount(args[0]).process();
}
}