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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LinkedHashMap线程的这个包装是否安全?如果不是,它怎么能变得线程安全?相关的知识,希望对你有一定的参考价值。

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

  1. 基本地图功能。 (只有基本的放置,获取,删除)
  2. 可以按照添加的顺序迭代地图的值。 (如在LinkedHashMap中)
  3. 是线程安全的。

目前使用通用实现,但在当前用例中,地图中只会有少数对象。并且很少发生添加/删除 - 名义上的添加只发生一次。

基本上这个容器应该为客户端提供通过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(); } 
答案

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

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 - 如果指定的键为null

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

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

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

另一答案

计划利用

java.util.concurrent.ConcurrentSkipListMap中

由所使用的密钥提供的“自然排序”就足够了。

另一答案

我认为这应该有效。

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

以上是关于LinkedHashMap线程的这个包装是否安全?如果不是,它怎么能变得线程安全?的主要内容,如果未能解决你的问题,请参考以下文章

如何保证LinkedHashMap以及它实现LRU缓存线程安全

如何保证LinkedHashMap以及它实现LRU缓存线程安全

LinkedHashMap

Collections.synchronizedMap(new LinkedHashMap());没有使 Map 线程安全

图解集合 6 : LinkedHashMap

LinkedHashMap和HashTable