7

我在这里发布了一个答案,其中代码演示了readputIfAbsent方法的使用:ConcurrentMap

ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong> ();

public long addTo(String key, long value) {
  // The final value it became.
  long result = value;
  // Make a new one to put in the map.
  AtomicLong newValue = new AtomicLong(value);
  // Insert my new one or get me the old one.
  AtomicLong oldValue = map.putIfAbsent(key, newValue);
  // Was it already there? Note the deliberate use of '!='.
  if ( oldValue != newValue ) {
    // Update it.
    result = oldValue.addAndGet(value);
  }
  return result;
}

这种方法的主要缺点是您必须创建一个新对象以放入地图中,无论它是否会被使用。如果物体很重,这可能会产生重大影响。

我突然想到这将是一个使用 Lambdas 的机会。我还没有下载 Java 8,或者直到它正式发布(公司政策)之前我才能下载,所以我无法对此进行测试,但这样的东西是否有效?

public long addTo(String key, long value) {
  return map.putIfAbsent( key, () -> new AtomicLong(0) ).addAndGet(value);
}

我希望使用 lambda 来延迟对 的评估,new AtomicLong(0)直到它实际确定应该创建它,因为它在地图中不存在。

如您所见,这更加简洁和实用。

基本上我想我的问题是:

  1. 这行得通吗?
  2. 还是我完全误解了 lambda?
  3. 有一天这样的事情可能会奏效吗?
4

4 回答 4

8

更新 2015-08-01

Java SE 8computeIfAbsent中确实添加了如下所述的方法。语义似乎非常接近预发布版本。

此外,界面computeIfAbsent中还添加了一大堆新的默认方法。Map当然,地图一般不支持原子更新,但新方法为 API 增加了相当大的便利。


您尝试做的事情非常合理,但不幸的是它不适用于当前版本的ConcurrentMap. 然而,增强功能正在进行中。新版本的并发库包括ConcurrentHashMapV8其中包含一个新方法computeIfAbsent。这几乎可以让你做你想做的事。使用这种新方法,您的示例可以重写如下:

public long addTo(String key, long value) {
    return map.computeIfAbsent( key, () -> new AtomicLong(0) ).addAndGet(value);
}

有关 的更多信息ConcurrentHashMapV8,请参阅 Doug Lea在并发兴趣邮件列表上的初始公告线程。线程中的几条消息是后续消息,它显示了一个与您尝试执行的操作非常相似的示例。(但请注意旧的 lambda 语法。该消息毕竟来自 2011 年 8 月。)这里是最近的 javadoc for ConcurrentHashMapV8.

这项工作旨在集成到 Java 8 中,但据我所知还没有。此外,这仍在进行中,名称和规格可能会更改等。

于 2013-02-16T17:59:36.460 回答
2

AtomicLong并不是真正的重物。对于较重的对象,我会考虑使用惰性代理,并在需要时为该代理提供 lambda 以创建对象。

class MyObject{
    void doSomething(){}
}

class MyLazyObject extends MyObject{
    Funktion create;
    MyLazyObject(Funktion create){
        this.create = create;
    }
    MyObject instance;
    MyObject getInstance(){
        if(instance == null)
            instance = create.apply();
        return instance;
    }
    @Override void doSomething(){getInstance().doSomething();}
}

public long addTo(String key, long value) {
  return map.putIfAbsent( key, new MyLazyObject( () -> new MyObject(0) ) );
}
于 2013-02-14T14:58:45.150 回答
2

不幸的是,这并不那么容易。您勾勒出的方法有两个主要问题: 1. 映射的类型需要从 to 更改Map<String, AtomicLong>Map<String, AtomicLongFunction>其中AtomicLongFunction一些函数接口具有一个不带参数并返回的单一方法AtomicLong)。2. 当您从地图中检索元素时,您需要每次都应用该功能才能AtomicLong摆脱它。这将导致每次检索它时都会创建一个新实例,这可能不是您想要的。

不过,拥有一个按需运行函数来填充缺失值的地图的想法是一个不错的想法,事实上,谷歌的 Guava 库有一个地图可以做到这一点。看看他们的MapMaker。事实上,代码将从 Java 8 lambda 表达式中受益:而不是

   ConcurrentMap<Key, Graph> graphs = new MapMaker()
       .concurrencyLevel(4)
       .weakKeys()
       .makeComputingMap(
           new Function<Key, Graph>() {
             public Graph apply(Key key) {
               return createExpensiveGraph(key);
             }
           });

你可以写

   ConcurrentMap<Key, Graph> graphs = new MapMaker()
       .concurrencyLevel(4)
       .weakKeys()
       .makeComputingMap((Key key) -> createExpensiveGraph(key));

或者

   ConcurrentMap<Key, Graph> graphs = new MapMaker()
       .concurrencyLevel(4)
       .weakKeys()
       .makeComputingMap(this::createExpensiveGraph);
于 2013-02-14T14:36:00.193 回答
2

请注意,使用 Java 8ConcurrentHashMap完全没有必要拥有AtomicLong值。您可以安全地使用ConcurrentHashMap.merge

ConcurrentMap<String, Long> map = new ConcurrentHashMap<String, Long>();

public long addTo(String key, long value) {
    return map.merge(key, value, Long::sum);
}

它更简单,也更快。

于 2015-08-02T04:06:41.020 回答