将线程安全性委托给ConcurrentMap和AtomicInteger

时间:2016-01-15 08:08:32

标签: java multithreading

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

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

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

问题: 是否有可能以某种方式委托线程安全并保留invariatns?

1 个答案:

答案 0 :(得分:4)

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

通过synchronizedSemaphore同步对地图的修改计数访问权限,以便当时只允许单个线程修改地图和计数元素。

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

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

修改 要100%明确,这是实现这一目标的最方便的方法:

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

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