Использование putIfAbsent как оператор короткого замыкания - PullRequest
0 голосов
/ 09 июня 2011

Можно ли использовать putIfAbsent или любой из его эквивалентов, например оператор короткого замыкания.

myConcurrentMap.putIfAbsent(key,calculatedValue)

Я хочу, чтобы, если уже было рассчитанное значение, его не нужно было вычислять снова. по умолчанию putIfAbsent будет по-прежнему выполнять вычисления каждый раз, даже при том, что фактически не будет сохранять значение снова.

Ответы [ 3 ]

1 голос
/ 09 июня 2011

Java не допускает какой-либо формы короткого замыкания, за исключением встроенных случаев, к сожалению - все вызовы метода приводят к полной оценке аргументов перед передачей управления методу. Таким образом, вы не можете сделать это с «нормальным» синтаксисом; вам нужно будет вручную обернуть вычисление в Callable или подобное, а затем явно вызвать его.


В этом случае мне трудно понять, как это все равно может работать. putIfAbsent работает на основе атомарной неблокирующей операции. Если бы он делал то, что вы хотите, последовательность событий была бы примерно такой:

  1. Проверьте, существует ли key на карте (в этом примере предполагается, что его нет)
  2. Оцените calculatedValue (вероятно, дорого, учитывая контекст вопроса)
  3. Положить результат на карту

Было бы невозможно, чтобы это было неблокирующим, если значение еще не существовало на втором шаге - два разных потока, вызывающих этот метод одновременно, могли бы работать правильно только в случае блокировки. На этом этапе вы можете просто использовать synchronized блоки с гибкостью реализации, которая влечет за собой; Вы можете определенно реализовать то, что вам нужно, с помощью некоторой простой блокировки, например:

private final Map<K, V> map = ...;

public void myAdd(K key, Callable<V> valueComputation) {
    synchronized(map) {
        if (!map.containsKey(key)) {
            map.put(key, valueComputation.call());
        }
    }
}
1 голос
/ 09 июня 2011

Вы можете поместить Future<V> объектов на карту. При использовании putIfAbsent будет присутствовать только один объект, и вычисление окончательного значения будет выполнено путем вызова Future.get() (например, FutureTask + Callable классами). Проверьте Параллелизм Java на практике для обсуждения использования этой техники. (Пример кода также в этот вопрос здесь на SO.

Таким образом, ваше значение вычисляется только один раз, и все потоки получают одно и то же значение. Доступ к карте не блокируется, хотя доступ к значению (через Future.get()) будет блокироваться, пока это значение не будет вычислено одним из потоков.

0 голосов
/ 09 июня 2011

Вы можете использовать Guava ComputingMap

ConcurrentMap<Key, Value> myConcurrentMap = new MapMaker()
  .makeComputingMap(
    new Function<Key, Value>() {
      public Value apply(Key key) {
        Value calculatedValue = calculateValue(key);
        return calculatedValue;
      }
  });
...