我有一个列表,我在我的函数synchronizedMap
中同名doMapOperation
。在这个函数中,我需要在地图中添加/删除项目并对这些对象执行昂贵的操作。我知道我不想在同步块中调用昂贵的操作,但我不知道在执行这些操作时如何确保映射处于一致状态。这样做的正确方法是什么?
这是我的初始布局,我肯定是错误的,因为你想避免在同步块中调用昂贵的操作:
public void doMapOperation(Object key1, Object key2) {
synchronized (synchronizedMap) {
// Remove key1 if it exists.
if (synchronizedMap.containsKey(key1)) {
Object value = synchronizedMap.get(key1);
value.doExpensiveOperation(); // Shouldn't be in synchronized block.
synchronizedMap.remove(key1);
}
// Add key2 if necessary.
Object value = synchronizedMap.get(key2);
if (value == null) {
Object value = new Object();
synchronizedMap.put(key2, value);
}
value.doOtherExpensiveOperation(); // Shouldn't be in synchronized block.
} // End of synchronization.
}
我想作为这个问题的延续,你将如何在循环中做到这一点?
public void doMapOperation(Object... keys) {
synchronized (synchronizedMap) {
// Loop through keys and remove them.
for (Object key : keys) {
// Check if map has key, remove if key exists, add if key doesn't.
if (synchronizedMap.containsKey(key)) {
Object value = synchronizedMap.get(key);
value.doExpensiveOperation(); // Shouldn't be here.
synchronizedMap.remove(key);
} else {
Object value = new Object();
value.doAnotherExpensiveOperation(); // Shouldn't here.
synchronizedMap.put(key, value);
}
}
} // End of synchronization block.
}
感谢您的帮助。
答案 0 :(得分:2)
您可以为synchronizedMap
创建一个包装器,并确保containsKey
,remove
和put
等操作是同步方法。然后,只会同步对地图的访问,而昂贵的操作可以在同步块之外进行。
另一个优点是,如果操作调用另一个同步映射方法,则可以将您的昂贵操作保留在synchronized块之外,以避免可能的死锁风险。
答案 1 :(得分:2)
您可以在同步块之外执行昂贵的操作,如下所示:
public void doMapOperation(Object... keys) {
ArrayList<Object> contained = new ArrayList<Object>();
ArrayList<Object> missing = new ArrayList<Object>();
synchronized (synchronizedMap) {
if (synchronizedMap.containsKey(key)) {
contained.add(synchronizedMap.get(key));
synchronizedMap.remove(key);
} else {
missing.add(synchronizedMap.get(key));
synchronizedMap.put(key, value);
}
}
for (Object o : contained)
o.doExpensiveOperation();
for (Object o : missing)
o.doAnotherExpensiveOperation();
}
唯一的缺点是,您可能会在将值从synchronizedMap
移除后对值执行操作。
答案 2 :(得分:1)
在第一个片段中:从if子句中声明两个值,并在if子句中分配它们。使if子句同步,并在外面调用昂贵的操作。
在第二种情况下做同样的事情,但在循环内部。 (在循环内同步)。当然,您可以在循环外只有一个synchronized
语句,只需填充List
对象即可调用昂贵的操作。然后,在同步块外部的第二个循环中,对列表中的所有值调用该操作。
答案 3 :(得分:1)
实际上只需一次同步命中即可完成所有操作。第一次删除可能是最简单的。如果您知道该对象存在,并且您知道remove是原子的,为什么不删除它,如果返回的内容不为null则调用昂贵的操作?
// Remove key1 if it exists.
if (synchronizedMap.containsKey(key1)) {
Object value = synchronizedMap.remove(key1);
if(value != null){ //thread has exclusive access to value
value.doExpensiveOperation();
}
}
对于看跌期权,因为它很昂贵而且应该是原子的,你几乎不走运,需要同步访问。我建议使用某种计算地图。看一下google-collections和MapMaker
您可以创建一个ConcurrentMap,它将根据您的密钥构建昂贵的对象,例如
ConcurrentMap<Key, ExpensiveObject> expensiveObjects = new MapMaker()
.concurrencyLevel(32)
.makeComputingMap(
new Function<Key, ExpensiveObject>() {
public ExpensiveObject apply(Key key) {
return createNewExpensiveObject(key);
}
});
这是一种memoization
的形式在这两种情况下,您根本不需要使用synchronized
(至少明确地)
答案 4 :(得分:1)
我们应该忘记效率低,大约97%的时间说: 过早优化是万恶之源。但我们不应该通过 在关键的3%中我们的机会。一个优秀的程序员不会 通过这样的推理被哄骗自满,他会明智地看 仔细阅读关键代码;但只有在那段代码之后 鉴定。 - 唐纳德克努特
你有一个方法,doMapOperation()。如果此方法继续进行块同步,您的表现如何?如果您不知道,那么当您获得性能良好的解决方案时,您将如何知道?您是否已准备好处理昂贵的操作的多个调用,即使它们已从地图中删除?
我并不是想要屈尊俯就,因为你可能比你所传达的更好地理解手头的问题,但似乎你正在进入一个你可能没准备好的优化水平,也可能不会是必要的。
答案 5 :(得分:1)
如果Map
中没有空值,则根本不需要containsKey()
来电:您可以使用Map.remove()
删除该项并告诉您是否在那里。所以同步块的真实内容只需要这样:
Object value = Map.remove(key);
if (value != null)
value.doExpensiveOperation();
else
{
value = new Value();
value.doExpensiveOperation();
map.put(key,value);
}
如果昂贵的操作本身不需要同步,即如果您不介意Map的其他客户端在操作时看到该值,则可以进一步简化:
Object value = Map.remove(key);
if (value == null)
{
value = new Value();
map.put(key,value);
}
value.doExpensiveOperation();
并且同步块可以在昂贵的操作之前终止。