ConcurrentHashMap:将集合存储为值时需要同步

时间:2017-06-30 20:42:14

标签: java multithreading concurrenthashmap

我有一些代码使用ConcurrentHashMap并想确认我正在做的事情是否正确。

映射将String存储为键,将集合(Set)存储为值。我最初有一个实现,我会使用锁定条带模式以原子方式执行某些操作(put,remove等),但后来意识到我可以使用computeIfAbsent/putIfAbsent(for put)和{{1} }(删除)。多个线程调用所有4个方法(get,put,removeKey,removeValue)。

有人能告诉我我的AFTER代码是否正确并且是线程安全的吗?提前谢谢!

computeIfAbsent

BEFORE

private final ConcurrentMap<K, Set<V>> map = new ConcurrentHashMap<>();
private final ConcurrentMap<K, Object> locks = new ConcurrentHashMap<>();

public Set<V> getValue(K key) {
    if (map.get(key) == null) return null;
    return map.get(key).stream().map(k -> k.getValue()).collect(Collectors.toSet());
}

AFTER

private void putValue(K key, V value) {
        Object lock = locks.putIfAbsent(key, new Object());
    if (lock == null) lock = locks.get(key);
        Set<V> existingSet = map.computeIfAbsent(key, k -> {
            Set<V> values = new ConcurrentSkipListSet<>(MY_COMPARATOR);
            values.add(value);
            return values;
        });
        if (existingSet != null) { // <- my guess is that this is unnecessary
            synchronized (lock) {
                map.get(key).add(expiringValue);
            }
        }
    }

public void removeKey(K key) {
    if (key == null) return;
    if (map.get(key) != null) {
        Object lock = locks.get(key);
        synchronized (lock) {
            map.remove(key);
        }
        locks.remove(key);
    }
}

public void removeValue(K key, V value) {
    if (key == null || value == null) return;
    if (map.get(key) != null) {
        Object lock = locks.get(key);
        synchronized (lock) {
            map.get(key).remove(value);
            if (map.get(key).size() == 0) {
                map.remove(key);
            }
        }
    }
}

2 个答案:

答案 0 :(得分:1)

至少有一个问题是您可能会使用putValue丢失值。 computeIfAbsent是线程安全的,但add()已不再存在。因此,您可以(根据您的逻辑)提出以下方案:

T1: computeIfAbsent creates the set for key K
T2: removes K
T1: performs add on the set, which is no longer in the map

答案 1 :(得分:1)

我认为你能做的最稳定就是这样:

private void putValue(K key, V value) {
    map.compute(key, (k, values) -> {
        if (values == null) {
            values = new ConcurrentSkipListSet<>(MY_COMPARATOR);
        }
        values.add(value);
        return values;
    });
}

public void removeKey(K key) {
    map.remove(key); // No change.
}

public void removeValue(K key, V value) {
    map.computeIfPresent(key, (k, values) -> {
        values.remove(value);
        return values.isEmpty() ? null : values;
    });
}

这些保证所有行动都是原子的。 putValue ConcurrentHashMapnull中的使用感觉有点笨拙,但null保证了行为,所以无论如何。

compute中返回$("input[type=radio]").on('click', function() { var secondClick = true; $(this).change(function() { secondClick = false; }); $(this).click(function() { if (secondClick) { $(this).prop("checked", false); } secondClick = true; }); });的重新映射功能正是您正在尝试执行的操作的正确方法:

  

如果函数返回<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <form name="myForm" id="myForm" method="POST" action="#"> <div class="formItem"> <label for="evaluation">Evaluation</label> <input type="radio" name="frm_eval" id="frm_eval1" value="0" />Yes <input type="radio" name="frm_eval" id="frm_eval2" value="1" />No </div> <div class="formItem"> <label for="conditions">Conditions</label> <input type="radio" name="frm_cond" id="frm_cond1" value="0" />Good <input type="radio" name="frmhs_cond" id="frm_cond2" value="1" />Fair <input type="radio" name="frm_cond" id="frm_cond3" value="2" />Poor </div> <div class="formItem"> <label for="responses">Responses</label> <input type="radio" name="frm_res" id="frm_res1" value="0" />Good <input type="radio" name="frm_res" id="frm_res2" value="1" />Fair <input type="radio" name="frm_res" id="frm_res3" value="2" />Poor </div> </form>,则删除映射。