集合的并发映射 - 在空时删除集合

时间:2012-08-30 11:25:26

标签: java concurrency java.util.concurrent

我正在尝试在java中编写一个线程安全的Map [K,Set [V]]实现。

  1. 如果向地图添加了唯一键,则应创建(并添加到)
  2. 新集
  3. 如果向地图添加了非唯一键,则应将现有的Set添加到。
  4. 如果从Set中删除了一个导致Set为空的值,则应该从地图中删除该条目以避免内存泄漏。
  5. 我想解决这个问题,而不需要同步整个事情
  6. 我在下面列出了一个失败的测试用例,如果您有解决方案,请告诉我。

    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));
                }
            }
        }
    }
    

5 个答案:

答案 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对应的当前值为空,则会将其删除。