Лямбды и putIfAbsent

Я разместил ответ здесь, где код, демонстрирующий использование метода putIfAbsent метода 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);
}

Я надеюсь использовать лямбду, чтобы отложить оценку new AtomicLong(0) до тех пор, пока не будет определено, что его следует создать, потому что он не существует на карте.

Как видите, это намного лаконичнее и функциональнее.

По сути, я полагаю, что мои вопросы:

  1. Будет ли это работать?
  2. Или я полностью неправильно истолковал лямбды?
  3. Может ли что-то подобное работать в один прекрасный день?

person OldCurmudgeon    schedule 14.02.2013    source источник
comment
Почему вы не можете скачать Java 8 и протестировать ее самостоятельно? Ваша компания запрещает вам устанавливать что-либо (даже в ознакомительных целях) на ваш рабочий компьютер? Как насчет того, чтобы попробовать это на своем личном?   -  person Simon Lehmann    schedule 14.02.2013
comment
@SimonLehmann - Кстати - После вашего комментария я установил Java 8, и в нем не только не было Lambdas (был дополнительный, который я должен был установить, но не удосужился до него), но и DBVisualiser перестал работать - поэтому я удалил его. Извиняюсь. Возможно, в следующем году.   -  person OldCurmudgeon    schedule 02.03.2013


Ответы (4)


ОБНОВЛЕНИЕ 2015-08-01

Метод computeIfAbsent, описанный ниже, действительно был добавлен в Java SE 8. Семантика очень близка к предварительной версии.

Кроме того, в интерфейс Map добавлен computeIfAbsent вместе с целой кучей новых методов по умолчанию. Конечно, карты вообще не могут поддерживать атомарные обновления, но новые методы значительно упрощают API.


То, что вы пытаетесь сделать, вполне разумно, но, к сожалению, оно не работает с текущей версией ConcurrentMap. Однако на подходе усовершенствование. Новая версия библиотеки параллелизма включает ConcurrentHashMapV8, который содержит новый метод computeIfAbsent. Это в значительной степени позволяет вам делать именно то, что вы хотите сделать. Используя этот новый метод, ваш пример можно переписать следующим образом:

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

Дополнительную информацию о ConcurrentHashMapV8 см. в первоначальной ветке объявлений в списке рассылки параллелизма. Несколько сообщений ниже по цепочке представляют собой дополнительное сообщение, которое показывает пример, очень похожий на то, что вы пытаетесь сделать. (Обратите внимание на старый синтаксис лямбда. В конце концов, это сообщение было от августа 2011 года.) А вот последний документ Java для ConcurrentHashMapV8.

Эта работа предназначена для интеграции в Java 8, но, насколько я понимаю, пока этого не произошло. Кроме того, это все еще находится в стадии разработки, имена и характеристики могут измениться и т. д.

person Stuart Marks    schedule 16.02.2013
comment
Спасибо, за исключением того факта, что он будет называться ConcurrentHashMapV8. Какая ужасная идея!! - person OldCurmudgeon; 16.02.2013
comment
Я не уверен, будет ли он по-прежнему называться CHMV8 к тому времени, когда он будет фактически интегрирован в Java 8. Я подозреваю, что Дуг Ли называет его CHMV8, чтобы его можно было использовать одновременно с CHM в приложениях, тестах, тестах для целей сравнения. . Леа говорит, что он предназначен для замены CHM. - person Stuart Marks; 17.02.2013
comment
@CupawnTae Спасибо за сообщение об этом. Отредактировано. - person Stuart Marks; 01.08.2015
comment
С JDK8 API проблема OP может быть решена еще проще с помощью Map.merge. Написал ответ по этому поводу. - person Tagir Valeev; 02.08.2015

AtomicLong на самом деле не тяжелый объект. Для более тяжелых объектов я бы рассмотрел ленивый прокси-сервер и предоставил ему лямбду для создания объекта, если это необходимо.

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) ) );
}
person Blank Chisui    schedule 14.02.2013
comment
Я использовал AtomicLong в качестве примера-заполнителя, я действительно думал о гораздо более тяжелых объектах. Это выглядит эффективно, но для реализации лени, которая в любом случае должна быть врожденной в лямбде, происходит очень много вещей. - person OldCurmudgeon; 14.02.2013
comment
Использование библиотеки Guava кажется проще. Но написание ленивых объектов делает вас независимыми от реализаций Map. Вы также можете создать общий InvocationHandler, который будет делегировать все вызовы методов, см. Прокси. Но вы правы, в большинстве случаев это было бы настоящим излишеством. - person Blank Chisui; 14.02.2013

К сожалению, это не так просто. Есть две основные проблемы с подходом, который вы набросали: 1. Тип карты должен измениться с Map<String, AtomicLong> на Map<String, AtomicLongFunction> (где AtomicLongFunction — это некоторый функциональный интерфейс, который имеет единственный метод, который не принимает аргументов и возвращает AtomicLong) . 2. Когда вы извлекаете элемент из карты, вам нужно каждый раз применять функцию, чтобы получить из него AtomicLong. Это приведет к созданию нового экземпляра каждый раз, когда вы его извлекаете, что вряд ли вам нужно.

Идея иметь карту, которая запускает функцию по запросу для заполнения пропущенных значений, тем не менее, хороша, и на самом деле в библиотеке Google Guava есть карта, которая делает именно это; см. их MapMaker. На самом деле этот код выиграет от лямбда-выражений Java 8: вместо

   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));

or

   ConcurrentMap<Key, Graph> graphs = new MapMaker()
       .concurrencyLevel(4)
       .weakKeys()
       .makeComputingMap(this::createExpensiveGraph);
person jacobm    schedule 14.02.2013
comment
Каким-то образом я просто знал, что мое решение было гораздо проще, чем в конечном итоге. Таким образом, я бы фактически добавил лямбду на карту, а не результат лямбды... ну да ладно. - person OldCurmudgeon; 14.02.2013

Обратите внимание, что при использовании Java 8 ConcurrentHashMap совершенно необязательно иметь значения 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);
}

Это намного проще, а также значительно быстрее.

person Tagir Valeev    schedule 02.08.2015