ConcurrentHashMap条件替换

时间:2009-09-08 16:44:02

标签: java multithreading collections

我希望能够有条件地替换ConcurrentHashMap中的值。那就是:

public class PriceTick {
   final String instrumentId;
   ...
   final long timestamp;
   ...

一个拥有ConcurrentHashMap的类(我们称之为TickHolder)(我们只需将其命名为 map )。

我希望能够实现条件put方法,这样如果没有键的条目,则插入新的条目,但是如果存在现有条目,则只有在插入时才会插入新条目新PriceTick中的时间戳值大于现有值

对于老式的HashMap解决方案,TickHolder会有一个put方法:

public void add(PriceTick tick) {
   synchronized(map) {
      if ((map.get(tick.instrumentId) == null)
         || (tick.getTimestamp() > map.get(tick.instrumentId).getTimestamp()) )
         map.put(tick.instrumentId, tick);
      }
}

使用ConcurrentHashMap,人们可能希望删除同步并使用一些原子方法,如replace,但这是无条件的。很明显,必须编写“条件替换”方法。

但是,由于测试和替换操作是非原子的,为了保证线程安全,它必须同步 - 但我最初读取的ConcurrentHashMap源使我认为外部同步及其内部锁不会很好地工作,所以至少每个执行结构更改的Map方法和包含类的执行必须由包含类同步......即使这样,我也会感到非常不安。

我想过继承ConcurrentHashMap,但这似乎是不可能的。它使用具有默认访问权限的内部最终类HashEntry,因此尽管ConcurrentHashMap不是final,但它不可扩展。

这似乎意味着我必须回到将TickHolder实现为包含一个老派的HashMap才能编写我的条件替换方法。

所以,问题是:我对上述内容是否正确?我(希望)是否遗漏了一些明显或微妙的东西,这会产生不同的结论?我真的希望能够在这里使用那种可爱的条纹锁定机制。

4 个答案:

答案 0 :(得分:4)

非确定性解决方案是循环replace()

do {
  PriceTick oldTick = map.get(newTick.getInstrumentId());
} while ((oldTick == null || oldTick.before(newTick)) && !map.replace(newTick.getInstrumentId(), oldTick, newTick);

虽然看起来很奇怪,但这是一种常见的建议模式。

答案 1 :(得分:2)

@cletus解决方案为我解决几乎完全相同的问题奠定了基础。我认为需要进行一些更改,但是如果oldTick为null则替换会抛出NullPointerException,如@hotzen所述

PriceTick oldTick;
do {
  oldTick = map.putIfAbsent(newTick.getInstrumentId());
} while (oldTick != null && oldTick.before(newTick) && !map.replace(newTick.getInstrumentId(), oldTick, newTick);

答案 2 :(得分:2)

正确答案应该是

PriceTick oldTick;
do {
  oldTick = map.putIfAbsent(newTick.getInstrumentId(), newTick);
  if (oldTick == null) {
    break;
  }
} while (oldTick.before(newTick) && !map.replace(newTick.getInstrumentId(), oldTick, newTick);

答案 3 :(得分:1)

作为替代方案,您是否可以创建TickHolder类,并将其用作地图中的值?它使地图使用起来稍微麻烦(现在获取一个值是map.getValue(key).getTick()),但它可以让你保持ConcurrentHashMap的行为。

public class TickHolder {
  public PriceTick getTick() { /* returns current value */
  public synchronized PriceTick replaceIfNewer (PriceTick pCandidate) { /* does your check */ }
}

你的put方法就像:

public void updateTick (PriceTick pTick) {
  TickHolder value = map.getValue(pTick.getInstrumentId());
  if (value != null) {
    TickHolder newTick = new TickHolder(pTick);
    value = map.putIfAbsent(pTick.getInstrumentId(), newTick);
    if (value == null) {
      value = newTick;
    }
  }
  value.replaceIfNewer(pTick);
}