LinkedHashmap线程的包装器是否安全?如果不是,它如何变得安全



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

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

当前使用通用实现,但是在当前用例中,地图中只有少数对象。而且添加/删除的发生极为不经常 - 名义上增加一次。

基本上,这个容器应为客户提供通过键和/或迭代值(带有订单保证(来查找单个值对象的能力。无论哪种情况,呼叫者都可能正在修改值对象,因此不能仅读取。最后,呼叫者可能来自多个线程。

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

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

我觉得迭代器部分阻止了它完全安全。我看到诸如ConturrentHashMap状态之类的类,即任何在对象上获取迭代器的客户端都必须在地图对象本身上手动同步。有没有一种方法可以使上面的代码线程安全,但仍允许客户端直接迭代器访问?即,我希望能够使用for-in loop,但是我无法在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中获得以下内容:

  • 只有少数项目
  • 很少会添加或删除项目

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

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

Java的ConturrentLinkedqueue文档的特别注释8:

迭代器的一致性很弱,返回元素,反映了迭代器的某个时候或以来的一定时刻。他们不会抛出同步的解放,并且可以与其他操作同时进行。队列中包含的元素,因为迭代器的创建将完全返回一次。

和:

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

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

要记住的另一件事是java.util.concurrent收藏不耐耐受性(concurrenthashmap.put(k,v(,concurrentlinkedqueue.add(v(,consurrenthashmap.get(k((。p>来自put(k,v(doc:

投掷: NullPoInterException-如果指定的密钥或值为null

来自add(v(doc:

投掷: NullPoInterException-如果指定的元素为null

从get(k(doc:

投掷: NullPoInterException-如果指定的密钥为null

我仍在考虑如何处理。似乎引入无效的事物显着复杂(就像往常一样(。

编辑:经过一些研究,我发现了以下内容:https://stackoverflow.com/a/9298113

我确实提出了上面分享的实现的扩展,但在实验环境之外,我对比赛状况感到不舒服。

计划利用

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

最新更新