java - LinkedHashMap 的这个包装线程安全吗?如果不是,它怎么能成为线程安全的呢?

标签 java multithreading thread-safety

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

  1. 基本 map 功能。 (仅限基本的放置、获取、删除)
  2. 可以按照添加顺序迭代 map 的值。 (如LinkedHashMap)
  3. 线程安全。

当前使用通用实现,但在当前用例中, map 中只会有少数对象。并且添加/删除发生的频率极低——名义上添加仅发生一次。

基本上,这个容器应该为客户端提供通过键查找单个值对象和/或迭代值的能力(具有顺序保证)。在任何一种情况下,调用者都可能会修改 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 之类的类声明任何在对象上获取迭代器的客户端都必须在 map 对象本身上手动同步。有没有办法使上面的代码线程安全,但仍然允许客户端直接迭代器访问?即,我希望能够使用 for-in 循环,但我无法在 MapWrapper 内的底层 map 上同步。

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方法可以根据客户期望的行为进行修改;这个最简单的实现只是一个建议。

特别值得注意的是ConcurrentLinkedQueue Java 8 的文档:

Iterators are weakly consistent, returning elements reflecting the state of the queue at some point at or since the creation of the iterator. They do not throw ConcurrentModificationException, and may proceed concurrently with other operations. Elements contained in the queue since the creation of the iterator will be returned exactly once.

还有:

This class and its iterator implement all of the optional methods of the Queue and Iterator interfaces.

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

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

来自 put(k, v) 文档:

Throws: NullPointerException - if the specified key or value is null

来自 add(v) 文档:

Throws: NullPointerException - if the specified element is null

来自 get(k) 文档:

Throws: NullPointerException - if the specified key is null

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

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

我确实想出了an extension of the implementation I shared above处理空值,但我会对实验设置之外的竞争条件感到不舒服。

关于java - LinkedHashMap 的这个包装线程安全吗?如果不是,它怎么能成为线程安全的呢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48940989/

相关文章:

java - 通过接收来自处理程序的消息来更新 UI

并发队列推送函数的 C++ 返回值

multithreading - Lombok 的构建器线程安全吗?

java - 鉴于已检查的错误不会在我的程序中抛出,这个未经检查的包装器是否可以接受

c - 在 C 代码库中查找全局/静态变量的工具

gcc - gcc 的 STL 空方法是线程安全的吗?

java - 使用 Java 将所有错误答案添加到数组中 - 如何?

java - HashMap中的桶数是什么意思?

java - JAVA-8 中堆上的字符串对象数

java - 基于触发器java运行任务