Java LinkedHashMap的这个包装器是线程安全的吗?如果不是,它如何成为线程安全的?

Java LinkedHashMap的这个包装器是线程安全的吗?如果不是,它如何成为线程安全的?,java,multithreading,thread-safety,Java,Multithreading,Thread Safety,我试图通过包装一个内置映射类来实现一个具有以下特性的类 基本地图功能。只有基本的放置、获取和移除 可以按添加的顺序迭代映射的值。就像LinkedHashMap一样 是线程安全的。 当前正在使用通用实现,但在当前用例中,地图中只有少数对象。而且添加/删除的情况极为罕见——名义上添加只发生一次 基本上,这一容器应该为客户端提供按键查找单个值对象和/或按顺序遍历值的能力。在任何一种情况下,调用方都可能修改Value对象,因此它不能是只读的。最后,调用方可能来自多个线程 这是我现在拥有的一个最小化版本:

我试图通过包装一个内置映射类来实现一个具有以下特性的类

基本地图功能。只有基本的放置、获取和移除 可以按添加的顺序迭代映射的值。就像LinkedHashMap一样 是线程安全的。 当前正在使用通用实现,但在当前用例中,地图中只有少数对象。而且添加/删除的情况极为罕见——名义上添加只发生一次

基本上,这一容器应该为客户端提供按键查找单个值对象和/或按顺序遍历值的能力。在任何一种情况下,调用方都可能修改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(); } 

我认为这应该行得通

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

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

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文档:

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

以及:

此类及其迭代器实现队列和迭代器接口的所有可选方法

假设您确保V是线程安全的,这个包装器集合应该确保容器线程安全

要记住的另一件事是java.util.concurrent集合不允许空

从putk,v文档:

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

从addv文档:

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

从getk文档:

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

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

编辑:经过一些研究,我发现:


我确实提出了处理空值的方法,但对于实验环境之外的比赛条件,我会感到不舒服。

计划利用

java.util.concurrent.ConcurrentSkipListMap


所使用的键提供的自然顺序就足够了。

为什么不使用ConcurrentHashMap?您需要通过新的ArrayListmap.values进行同步,以确保线程安全。我能想到的唯一替代方法是公开一个同步的forEach方法,而不是一个迭代器?将java.util.concurrent.ConcurrentSkipListMap与维护插入顺序的节点包装器和比较器一起使用。所有的艰苦工作都会为您解决。@JimGarrison您想要什么样的比较器?为什么不使用Map Map=Collections.synchronizedMapnew LinkedHashMap?此解决方案存在许多不同的竞争条件。例如,hasNext可能返回true,但最后一项可能被删除,下一项将抛出。这是一个很难解决的问题。