【问题标题】:Delegating thread-safety to ConcurrentMap and AtomicInteger将线程安全委托给 ConcurrentMap 和 AtomicInteger
【发布时间】:2016-04-20 18:54:56
【问题描述】:

我需要提供以下容器的线程安全实现:

public interface ParameterMetaData<ValueType> {
    public String getName();
}

public interface Parameters {
    public <M> M getValue(ParameterMetaData<M> pmd);
    public <M> void put(ParameterMetaData<M> p, M value);
    public int size();
}

问题是 size 方法应该返回当前包含在 Parameters 实例中的准确个参数。所以,我的第一次尝试是尝试如下委派线程安全:

public final class ConcurrentParameters implements Parameters{

    private final ConcurrentMap<ParameterMetaData<?>, Object> parameters = 
                                                    new ConcurrentHashMap<>();
    //Should represent the ACCURATE size of the internal map
    private final AtomicInteger size = new AtomicInteger();

    @Override
    public <M> M getValue(ParameterMetaData<M> pmd) {
        @SuppressWarnings("unchecked")
        M value = (M) parameters.get(pmd);
        return value;
    }

    @Override
    public <M> void put(ParameterMetaData<M> p, M value){
        if(value == null)
            return;
        //The problem is in the code below
        M previous = (M) parameters.putIfAbsent(p, value);
        if(previous != null)
            //throw an exception indicating that the parameter already exists
        size.incrementAndGet();
    }

    @Override
    public int size() {
        return size.intValue();
    }

问题是我不能只在ConcurrentHashMap 实例上调用parameters.size() 来返回实际大小,因为该操作在没有锁定的情况下执行遍历,并且没有保证它将检索实际大小。在我的情况下这是不可接受的。所以,我决定维护包含大小的字段。

问题:是否有可能以某种方式委托线程安全并保持不变性?

【问题讨论】:

  • 如果没有外部锁定,您无法获得比 ConcurrentHashMap 已经提供的更高的精度,因为您无法保证原子操作。

标签: java multithreading


【解决方案1】:

您想要实现的结果是非原子的。您想修改地图,然后获取在单线程范围内一致的元素计数。实现这一点的唯一方法是通过同步对地图的访问来使此流程成为“原子操作”。这是确保计数不会因另一个线程中的修改而改变的唯一方法。

通过 synchronizedSemaphore 同步对 map 的 modify-count 访问,以仅允许单个线程同时修改 map 和 count 元素。

这里使用附加字段作为计数器并不能保证线程安全,因为在map修改之后和计数器操作之前,其他线程实际上可以修改map,计数器值将无效。

这就是为什么 map 在内部不保持其大小而是必须遍历元素的原因 - 以便在给定的时间点给出最准确的结果。

编辑: 100% 清楚,这是实现这一目标的最方便的方法:

synchronized(yourMap){
    doSomethingWithTheMap();
    yourMap.size();
}

因此,如果您将每个 map 操作更改为此类块,您将保证 size() 将返回准确的元素计数。唯一的条件是所有的数据操作都是使用同步块完成的。

【讨论】:

  • 顺便说一句,notThreadSafe HashMap 通过将其存储在字段中来在内部跟踪大小。所以,在某种程度上,唯一的方法是应用监视器模式并同步所有内容,对吧?因此在 CoincurrentMap 和 size-counter 中不需要。 HashMap 会很好用。
  • 这就是为什么它是 NTS :) 如果您将使用 synchronized 块来访问映射和读取计数,那么是的,它将由 TS 并且计数将是准确的。但是如果你只同步地图操作方法并在同步范围之外调用getSize(),这仍然是NTS,不保证是一致的。
  • 好的,知道了,非常感谢。但是指望 HashMap 在检索大小时不会执行遍历是否可靠?或者通过将其放入字段中来自己跟踪大小会更安全?
  • @St.Antario 以加快速度(但对于小地图,您可能不必这样做)您可以自己跟踪元素,以便随时阅读。
  • @St.Antario 这就是我的意思。但是你可以做到这一点。
猜你喜欢
  • 2021-08-25
  • 1970-01-01
  • 2019-10-06
  • 1970-01-01
  • 2011-04-01
  • 1970-01-01
  • 2011-07-01
  • 2017-08-23
  • 1970-01-01
相关资源
最近更新 更多