具有昂贵操作的Collection上的Java同步

时间:2011-10-26 17:19:58

标签: java collections synchronization

我有一个列表,我在我的函数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.
}

感谢您的帮助。

6 个答案:

答案 0 :(得分:2)

您可以为synchronizedMap创建一个包装器,并确保containsKeyremoveput等操作是同步方法。然后,只会同步对地图的访问,而昂贵的操作可以在同步块之外进行。

另一个优点是,如果操作调用另一个同步映射方法,则可以将您的昂贵操作保留在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();

并且同步块可以在昂贵的操作之前终止。