LinkedHashMap线程的这个包装是否安全?如果不是,它怎么能变得线程安全?

时间:2018-02-23 04:12:13

标签: java multithreading thread-safety

我试图通过包装一个内置的Map类来实现具有以下功能的类。

  1. 基本地图功能。 (只有基本的放置,获取,删除)
  2. 可以按照添加顺序迭代地图的值。 (如在LinkedHashMap中)
  3. 线程安全。
  4. 目前使用通用实现,但在当前用例中,地图中只会有少数几个对象。并且很少发生添加/删除 - 名义上的添加只发生一次。

    基本上这个容器应该为客户端提供通过Key查询单个Value对象的能力和/或迭代值(带有顺序保证)。在任何一种情况下,调用者都可能正在修改Value对象,因此它不能是只读的。最后,来电者可能来自多个线程。

    这是我现在所拥有的最小化版本:

    public class MapWrapper<K, V> implements Iterable<V>
    {
        private Map<K, V> map = new LinkedHashMap<K, V>();
    
        public void add(K key, V value)
        {
            // Does some other stuff
    
            synchronized (map)
            {
                map.put(key, value);
            }
        }
    
        public V get(K key)
        {
            V retVal;
            synchronized (map)
            {
                retVal = map.get(key);
            }
            return retVal;
        }
    
        @Override
        public Iterator<V> iterator()
        {
            List<V> values = new ArrayList<V>(map.values());
            return values.iterator();
        }
    }
    

    我觉得迭代器部分阻止了它完全是线程安全的。我看到诸如ConcurrentHashMap之类的类声明任何客户端在对象上获取迭代器必须在地图对象本身上手动同步。有没有办法使代码上面的线程安全,但仍允许客户端直接访问迭代器?即,我希望能够使用for-in循环,但我无法在MapWrapper中的底层地图上进行同步。

    MapWrapper<String, Object> test = new MapWrapper<String,Object>();
    test.add("a", new Object());
    test.add("c", new Object());
    for (Object o: test) { o.setSomething(); } 
    

3 个答案:

答案 0 :(得分:0)

我相信以下内容通过保留有序和散列引用来解决问题,同时以最小的努力保持线程安全:

import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

public class OrderedConcurrentHashMap<K, V> implements Iterable<V>
{
    private ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>();
    private ConcurrentLinkedQueue<V> queue = new ConcurrentLinkedQueue<>();

    public void add(K key, V value)
    {
        map.put(key, value);
        queue.add(value);
    }

    public V get(K key)
    {
        return map.get(key);
    }

    public boolean remove(K key)
    {
        return queue.remove(map.remove(key));
    }

    @Override
    public Iterator<V> iterator()
    {
        return queue.iterator();
    }
}

从OP获得以下内容:

  • 只有少数几件物品
  • 很少会添加或删除项目

这可能是仅使用内置集合和并发实用程序的最佳解决方案。

此处的remove方法可以根据客户期望的行为进行修改;这个最简单的实现只是一个建议。

来自Java 8的ConcurrentLinkedQueue文档的特别注意事项:

  

迭代器是弱一致的,在迭代器创建时或之后的某个时刻返回反映队列状态的元素。它们不会抛出ConcurrentModificationException,并且可能与其他操作同时进行。自创建迭代器以来队列中包含的元素将只返回一次。

  

该类及其迭代器实现了Queue和Iterator接口的所有可选方法。

假设您确保V是线程安全的,则此包装器集合应确保容器线程安全。

要记住的另一件事是java.util.concurrent集合不是空容忍的(ConcurrentHashMap.put(k, v)ConcurrentLinkedQueue.add(v)ConcurrentHashMap.get(k))。

来自put(k,v)doc:

  

抛出:   NullPointerException - 如果指定的键或值为null

来自add(v)doc:

  

抛出:   NullPointerException - 如果指定的元素为null

来自get(k)doc:

  

抛出:   NullPointerException - 如果指定的键为空

我还在考虑如何处理这件事。 似乎引入空值会使事情变得非常复杂(一如既往)。

编辑:经过一些研究,我发现了这个:https://stackoverflow.com/a/9298113

我确实提出了处理空值的an extension of the implementation I shared above,但我对实验环境之外的种族条件感到不舒服。

答案 1 :(得分:0)

计划利用

java.util.concurrent.ConcurrentSkipListMap中

&#34;自然顺序&#34;由使用的密钥提供就足够了。

答案 2 :(得分:-2)

我认为这应该有用。

public class MapWrapper<K, V> implements Iterable<V> {
    private Map<K, V> map = new LinkedHashMap<K, V>();
    private int currentSize = 0;

    public void add(K key, V value) {
        // Does some other stuff

        synchronized (map) {
            map.put(key, value);
            currentSize++;
        }
    }

    public V get(K key) {
        V retVal;
        synchronized (map) {
            retVal = map.get(key);
            currentSize--;
        }
        return retVal;
    }

    @Override
    public Iterator<V> iterator() {
        return new SyncIterator();
    }

    // Inner class example
    private class SyncIterator implements Iterator<V> {

        private int currentIndex = 0;

        @Override
        public boolean hasNext() {
            return currentIndex < currentSize;
        }

        @Override
        public V next() {

            synchronized (map) {

                List<V> values = new ArrayList<V>(map.values());
                return values.get(currentIndex++);
            }
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
}