我有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);
}
答案 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)更多的密钥(数百到数百万),这个循环很少会循环多次,但是必须再次尝试。