我需要提供以下容器的线程安全实现:
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?
答案 0 :(得分:4)
您想要实现的结果是非原子的。您想要修改map,然后获取在单线程范围内一致的元素计数。实现这一目标的唯一方法是通过同步对地图的访问来使此流程成为“原子操作”。这是确保计数不会因为在另一个线程中进行修改而改变的唯一方法。
通过synchronized
或Semaphore
同步对地图的修改计数访问权限,以便当时只允许单个线程修改地图和计数元素。
使用附加字段作为计数器不保证此处的线程安全,因为在映射修改之后和计数器操作之前,其他线程实际上可以修改映射,并且计数器值将无效。
这就是为什么map不会在内部保持其大小但必须遍历元素的原因 - 在给定的时间点给出最准确的结果。
修改强> 要100%明确,这是实现这一目标的最方便的方法:
synchronized(yourMap){
doSomethingWithTheMap();
yourMap.size();
}
因此,如果您将每个地图操作更改为此类块,则可以保证size()
将返回准确的元素数。唯一的条件是所有数据操作都是使用这样的同步块完成的。