数据行级别的并发

时间:2011-10-08 22:56:31

标签: java jdk1.6

我有HashMap并希望分别同步每个行/条目以最大化并发性,因此这样许多线程可以同时访问HashMap但没有两个线程或更多可以同时访问相同的行/条目。

我在代码中执行了以下操作,但我不确定它是否正确:

/* Lock/synchronize the data to this key, (skey is a key of type String) */
synchronized (aHashMap.get(skey)) {

    /* write the data (data is Integer) */
    aHashMap.put(skey, data);

}

3 个答案:

答案 0 :(得分:2)

适当的解决方案在很大程度上取决于您的特定问题。如果您的所有主题都可以更新Map中的任何条目,那么首先要尝试的是ConcurrentHashMap

在这种情况下,您描述的操作将替换为:

data = ... compute ...
aHashMap.replace(skey, data);

使用ConcurrentHashMap解决了数据竞争问题,但仍然存在一个问题。如果另一个线程同时更新同一个密钥,则其中一个计算将丢失。如果你对此感到满意,那很好。否则,您可以:

do {
  oldData = aHashMap.get(skey);
  data = ... compute (maybe based on oldData) ... 
  boolean success = aHashMap.replace(skey, oldData, data);
} while(!success);

在这种情况下,只有在数据没有改变(并且替换是原子的)时,替换才会成功。如果失败,您可以将所有内容放入do while循环中再次尝试,可能基于更新的值。

另外,注意不要在地图获取和替换之间产生任何副作用。计算应该只创建一个全新的“数据”对象。如果更新“oldData”对象或其他一些共享数据,则会得到意外结果。

如果您确实有副作用,一种方法是按照以下方式进行键级锁定:

synchronized(skey) {
  data = ... compute ... 
  aHashMap.replace(skey, data);
}

即使在这种情况下,仍然需要ConcurrentHashMap。此外,这不会阻止其他一些代码更新地图中的该键。更新密钥的所有代码都需要锁定它。

此外,如果您在“... compute ...”中更新oldData并且值在地图中不唯一,则这将不是线程安全的。如果你想在那里更新oldData,请用另一个同步覆盖它。

如果这样做的诀窍和你的内容与表现,不要再看了。

如果线程只更新值,请不要更改键,然后您可以尝试将对转换为对象并使用与Map不同的值。例如,您可以将对象集拆分为多个集合,然后将它们提供给您的线程。或者也许使用ParallelArray。但我可能会在这里离题......:)

答案 1 :(得分:1)

你应该真正使用ConcurrentHashMap类。

你的解决方案有问题:只要另一个线程将一个项目放入地图,导致散列图扩展,你可能会丢失更新。此外,它显然取决于尊重锁的hashmap的所有用户,如果有人使用该对象锁定其他东西,你将遇到一大堆问题。

答案 2 :(得分:1)

您所采用的方法的问题是您正在替换lcok对象。这意味着每个尝试执行更新的线程都可以锁定不同的对象,这样做无效。

我会像其他人建议的那样使用ConcurrentHashMap。您的操作将替换该值以将其锁定,或者任何其他对象不会在此处添加任何值。

ConcurrentMap<Integer, Value> map = new ConcurrentMap<Integer, Value>();

// thread safe write of the data. No locks required.
map.put(skey, data);

编辑:

如果您有get()并且想要更新可变值,那么

Value value = map.get(skey);
synchronized(value) {
    value.changeValue();
}

在这种情况下,无需替换相同的值。值需要自己的同步或锁定,因为它不是线程安全的。


如果要“更新”不可变值,则必须使用循环来继续尝试更新。这假设这样做没有副作用。

while(true) {
   Value value = map.get(skey);
   Value value2 = compute(value);
   if(map.replace(skey, value, value2)) break;
}

此循环将继续迭代,直到它成功替换它预期要替换的值。鉴于你将拥有比核心(4-24)更多的密钥(数百到数百万),这个循环很少会循环多次,但是必须再次尝试。