我正在尝试在java中编写一个线程安全的Map [K,Set [V]]实现。
我在下面列出了一个失败的测试用例,如果您有解决方案,请告诉我。
package org.deleteme;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import junit.framework.Assert;
import org.junit.Test;
public class ConcurrentSetMapTest {
public static class ConcurrentSetMap<K, V> {
private final ConcurrentMap<K, Set<V>> map = new ConcurrentHashMap<K, Set<V>>();
public void add(K key, V value) {
Set<V> set = map.get(key);
if (set != null) {
set.add(value);
} else {
Set<V> candidateSet = createConcurrentSet(value);
set = map.putIfAbsent(key, candidateSet);
if (set != null) {
// candidate set not accepted, use existing
set.add(value);
}
}
}
public void remove(K key, V value) {
Set<V> set = map.get(key);
if (set != null) {
boolean removed = set.remove(value);
if (removed && set.isEmpty()) {
// this is not thread-safe and causes the test to fail
map.remove(key, set);
}
}
}
public boolean contains(K key, V value) {
Set<V> set = map.get(key);
if (set == null) {
return false;
}
return set.contains(value);
}
protected Set<V> createConcurrentSet(V element) {
Set<V> set = Collections.newSetFromMap(new ConcurrentHashMap<V, Boolean>());
set.add(element);
return set;
}
}
@Test
public void testThreadSafe() throws InterruptedException, ExecutionException {
ConcurrentSetMap<String, String> setMap = new ConcurrentSetMap<String, String>();
ExecutorService executors = Executors.newFixedThreadPool(4);
List<Future<?>> futures = new ArrayList<Future<?>>();
futures.add(executors.submit(new TestWorker(setMap, "key1")));
futures.add(executors.submit(new TestWorker(setMap, "key1")));
futures.add(executors.submit(new TestWorker(setMap, "key2")));
futures.add(executors.submit(new TestWorker(setMap, "key2")));
for (Future<?> future : futures) {
future.get();
}
}
public static class TestWorker implements Runnable {
ConcurrentSetMap<String, String> setMap;
String key;
public TestWorker(ConcurrentSetMap<String, String> setMap, String key) {
super();
this.setMap = setMap;
this.key = key;
}
public void run() {
int sampleSize = 100000;
for (int i = 0; i < sampleSize; ++ i) {
// avoid value clashes with other threads
String value = Thread.currentThread().getName() + i;
Assert.assertFalse("Should not exist before add", setMap.contains(key, value));
setMap.add(key, value);
Assert.assertTrue("Should exist after add", setMap.contains(key, value));
setMap.remove(key, value);
Assert.assertFalse("Should not exist after remove", setMap.contains(key, value));
}
}
}
}
答案 0 :(得分:4)
不要写这样的地图,使用别人的。我会使用Guava的SetMultimap实现之一,例如HashMultimap,并使用Multimaps.synchronizedSetMultimap进行同步。
答案 1 :(得分:2)
这是一个完全并发且线程安全的实现:
public class ConcurrentSetMap<K,V> {
private final ConcurrentMap<K, Set<V>> _map = new ConcurrentHashMap<K, Set<V>>();
public void add(K key, V value) {
Set<V> curSet = _map.get(key);
while(true) {
if((curSet != null) && curSet.contains(value)) {
break;
}
Set<V> newSet = new HashSet<V>();
newSet.add(value);
if(curSet == null) {
curSet = _map.putIfAbsent(key, newSet);
if(curSet != null) {
continue;
}
} else {
newSet.addAll(curSet);
if(!_map.replace(key, curSet, newSet)) {
curSet = _map.get(key);
continue;
}
}
break;
}
}
public void remove(K key, V value) {
Set<V> curSet = _map.get(key);
while(true) {
if((curSet == null) || !curSet.contains(value)) {
break;
}
if(curSet.size() == 1) {
if(!_map.remove(key, curSet)) {
curSet = _map.get(key);
continue;
}
} else {
Set<V> newSet = new HashSet<V>();
newSet.addAll(curSet);
newSet.remove(value);
if(!_map.replace(key, curSet, newSet)) {
curSet = _map.get(key);
continue;
}
}
break;
}
}
public boolean contains(K key, V value) {
Set<V> set = _map.get(key);
return set != null && set.contains(value);
}
}
将时间与@PeterLawrey的回答(在我的方框中)进行比较,他需要2.9秒,这需要1.4秒。
答案 2 :(得分:2)
我设法解决了我的问题:)
我在最初的帖子中没有提到我需要从集合中快速读取,我不太关心写入速度。出于这个原因,我提出了一种同步写访问但不需要同步读访问的解决方案。下面的代码现在通过我的测试用例。
感谢大家的建议。
public static class ConcurrentSetMap<K, V> {
private final ConcurrentMap<K, Set<V>> map = new ConcurrentHashMap<K, Set<V>>();
public synchronized void add(K key, V value) {
Set<V> set = map.get(key);
if (set != null) {
set.add(value);
} else {
map.put(key, createConcurrentSet(value));
}
}
public synchronized void remove(K key, V value) {
Set<V> set = map.get(key);
if (set != null) {
set.remove(value);
if (set.isEmpty()) {
map.remove(key);
}
}
}
public boolean contains(K key, V value) {
return get(key).contains(value);
}
public Set<V> get(K key) {
Set<V> set = map.get(key);
return set == null ? Collections.<V> emptySet() : set;
}
protected Set<V> createConcurrentSet(V value) {
Set<V> set = Collections.newSetFromMap(new ConcurrentHashMap<V, Boolean>());
set.add(value);
return set;
}
}
答案 3 :(得分:1)
当您执行需要原子化的多个操作时,您需要使用锁定。
public class SynchronousMultiMap<K, V> {
private final Map<K, Set<V>> map = new LinkedHashMap<K, Set<V>>();
public synchronized void add(K key, V value) {
Set<V> set = map.get(key);
if (set == null)
map.put(key, set = new LinkedHashSet<V>());
set.add(value);
}
public synchronized void remove(K key, V value) {
Set<V> set = map.get(key);
if (set == null) return;
set.remove(value);
if (set.isEmpty()) map.remove(key);
}
public synchronized boolean contains(K key, V value) {
Set<V> set = map.get(key);
return set != null && set.contains(value);
}
@Test
public void testThreadSafe() throws ExecutionException, InterruptedException {
ExecutorService executors = Executors.newFixedThreadPool(3);
List<Future<?>> futures = new ArrayList<Future<?>>();
SynchronousMultiMap<String, Integer> setMap = new SynchronousMultiMap<String, Integer>();
int sampleSize = 1000000;
String[] keys = "key1,key2,key3,key4".split(",");
for (int i = 0; i < 3; i++)
futures.add(executors.submit(new TestWorker(setMap, keys, sampleSize, i)));
executors.shutdown();
for (Future<?> future : futures) {
future.get();
}
}
static class TestWorker implements Runnable {
final SynchronousMultiMap<String, Integer> setMap;
final String[] keys;
final int sampleSize;
final int value;
public TestWorker(SynchronousMultiMap<String, Integer> setMap, String[] keys, int sampleSize, int value) {
super();
this.setMap = setMap;
this.keys = keys;
this.sampleSize = sampleSize;
this.value = value;
}
public void run() {
for (int i = 0; i < sampleSize; i += keys.length) {
for (String key : keys) {
boolean contains = setMap.contains(key, value);
if (contains)
Assert.assertFalse("Should not exist before add", contains);
setMap.add(key, value);
boolean contains2 = setMap.contains(key, value);
if (!contains2)
Assert.assertTrue("Should exist after add", contains2);
setMap.remove(key, value);
boolean contains3 = setMap.contains(key, value);
if (contains3)
Assert.assertFalse("Should not exist after remove", contains3);
}
}
}
}
}
需要0.35秒才能运行。使用sampleSize=1000000
需要&lt; 8秒。
答案 4 :(得分:1)
使用<{strong} 致电 致电 如果与ConcurrentMap
ConcurrentSkipListSet
。{/ p>
ConcurrentMap.putIfAbsent()
为密钥添加集合,如果它不存在。ConcurrentMap.remove( key, emptySet )
,其中emptySet
为空ConcurrentSkipListSet
。key
对应的当前值为空,则会将其删除。