Java 安全地更新ConcurrentHashMap和AtomicInteger

Java 安全地更新ConcurrentHashMap和AtomicInteger,java,concurrency,concurrenthashmap,Java,Concurrency,Concurrenthashmap,我必须将单词及其对应的整数索引存储在哈希映射中。哈希映射将同时更新 例如:假设单词列表是{a,b,c,a,d,e,a,d,e,b} 哈希映射将包含以下键值对 a:1 b:2 c:3 d:4 e:5 其代码如下: public class Dictionary { private ConcurrentMap<String, Integer> wordToIndex; private AtomicInteger maxIndex; public

我必须将单词及其对应的整数索引存储在哈希映射中。哈希映射将同时更新

例如:假设
单词列表是
{a,b,c,a,d,e,a,d,e,b}
哈希映射将包含以下键值对

a:1
b:2
c:3
d:4
e:5
其代码如下:

public class Dictionary {

private ConcurrentMap<String, Integer>  wordToIndex;
private AtomicInteger                   maxIndex;

public Dictionary( int startFrom ) {
    wordToIndex = new ConcurrentHashMap<String, Integer>();
    this.maxIndex = new AtomicInteger(startFrom);
}


public void insertAndComputeIndices( List<String> words ) {

    Integer index;
    //iterate over the list of words
    for ( String word : words ) {
        // check if the word exists in the Map
        // if it does not exist, increment the maxIndex and put it in the
        // Map if it is still absent
        // set the maxIndex to the newly inserted index

        if (!wordToIndex.containsKey(word)) {
            index = maxIndex.incrementAndGet();

            index = wordToIndex.putIfAbsent(word, index);
            if (index != null)
                maxIndex.set(index);
        }
    }
}
公共类字典{
私有ConcurrentMap wordToIndex;
私有原子整数最大索引;
公共词典(int startFrom){
wordToIndex=新的ConcurrentHashMap();
this.maxIndex=新的原子整数(startFrom);
}
public void insertandcomputeindicates(列出单词){
整数指数;
//反复浏览单词列表
for(字符串字:字){
//检查地图中是否存在该单词
//如果不存在,则增加maxIndex并将其放入
//如果它仍然不存在,请绘制地图
//将maxIndex设置为新插入的索引
如果(!wordToIndex.containsKey(word)){
index=maxIndex.incrementAndGet();
索引=wordToIndex.putIfAbsent(单词,索引);
如果(索引!=null)
maxIndex.set(index);
}
}
}
我的问题是上面的类是否是线程安全的? 基本上,在这种情况下,原子操作应该是增加
maxIndex
,然后在没有单词的情况下将其放入哈希映射中


在这种情况下,有更好的方法实现并发吗?

没有。如果有两种方法a和B,都是线程安全的,当然这并不意味着按顺序调用a和B也是线程安全的,因为一个线程可以在函数调用之间中断另一个方法。这里发生的情况如下:

    if (!wordToIndex.containsKey(word)) {
        index = maxIndex.incrementAndGet();

        index = wordToIndex.putIfAbsent(word, index);
        if (index != null)
            maxIndex.set(index);
    }
线程A验证wordToIndex不包含单词“dog”并在if中继续。在添加单词“dog”之前,线程B还发现“dog”不在映射中(A尚未添加),因此它也在if中继续。现在,您尝试插入两次单词“dog”


当然,putIfAbsent将保证只有一个线程可以添加它,但我认为您的目标是不让两个线程使用同一个键同时进入if。

显然,另一个线程可以看到
maxIndex
递增,然后受到重击

假设这就是映射的全部内容(特别是,没有删除),那么您可以尝试将单词放入映射中,如果成功,则仅递增

    Integer oldIndex = wordToIndex.putIfAbsent(word, -1);
    if (oldIndex == null) {
        wordToIndex.put(word, maxIndex.incrementAndGet());
    }

(对于单个<代码>放置<代码>,使用某种类型的替换来代替<代码>整数< /代码>)

是你应该考虑使用的东西。


您应该将所有需要发生的代码作为
事务
包装在
同步(此)
块中。

其他答案是正确的——类中有非线程安全字段。首先,您应该做的是确保

如何实现线程化

1) 我会确保内部的一切都是私有的,尽管这不是线程安全代码的要求

2) 找到任何访问器方法,确保在修改全局对象的状态时(或至少在同步IF块时)对它们进行snychronized


3) 测试死锁或坏计数,这可以在单元测试中通过确保10000个线程插入后maxIndex的值是正确的来实现,例如…

插入和计算条件
只是混合关注点的气味,任何作为方法名称的东西都是不好的
部分应该是一个实现细节,与客户机代码无关。这可能不起作用……如果线程A和B将oldIndex视为null,那么两个线程都可能首先递增maxIndex,然后放入单词……因此我将看不到连续maxIndex……如果错误,请更正我已在使用AtomicInteger……如果我要使用同步然后我会直接使用HashMap,而不是ConcurrentHashMap,我会有一个很好的解决方案……但我想我想使用ConcurrentHashMap
ConcurrentHashMap
并不是指你认为它的意思,它意味着各个方法都是同步的,但如果你称它们为独立的按照顺序,您仍然必须将您想要的原子事务包装在
synchronized
中。这背后没有什么魔力。我的目标也是我关心的是,对于同一个单词,maxIndex不应该增加两次(在上述场景中这是可能的)…如果线程A和B尝试放置“dog”,两者都可以到达if块内部,但都可能增加maxIndex,因此我将有一个索引浪费…这就是我想要避免的