Size: a a a

2020 August 27

II

Iurii Iurchenko in JUG NN
тоже воспроизвёл
источник

II

Iurii Iurchenko in JUG NN
забавно, computeIfAbsent уходит в бесконечный цикл
источник

II

Iurii Iurchenko in JUG NN
Map<Key, Integer> m = new ConcurrentHashMap<>();
int r = m.computeIfAbsent(Key(1), k1 -> m.computeIfAbsent(Key(2), k2 -> 1) + 1);

у Key hashCode фиксированный для простоты воспроизведения.
источник

II

Iurii Iurchenko in JUG NN
под рукой был гуава кэш - ему так норм
источник

SK

Sergey Kapralov in JUG NN
Вощем ладно, я наверное сумбурно выражался, надо было с гистами наверно идти сюда, а не с голым вопросом. Вернусь попозже.
источник

MB

Maxim Belov in JUG NN
это конечно интересная инфа, честно говоря не задумывался об этом никогда, но тебе не кажется что ты кэш используешь не для того, для чего он предназаначен? Ну типа задача кэша как ни странно - кэшировать, а пытаешься навернуть на него еще и логику на вычисление одного значения на базе других значений, которые могут быть не инициализированны на этот момент и ловишь дедлок. Может быть просто вынести эту логику непосредственно из кэша, не? Ну т.е. ничего же не мешает заменить первый вариант на второй
def map = new ConcurrentHashMap<String, String>()
map.computeIfAbsent("A", { key ->
   return map.computeIfAbsent("B", {k -> "hello"}) + "ForA"
})

println(map)

// --------

def map2 = new ConcurrentHashMap<String, String>()
def bValue = map2.computeIfAbsent("B", {k -> "hello"})
map2.computeIfAbsent("A", {key -> bValue + "ForA"})

println(map2)
источник

MB

Maxim Belov in JUG NN
может я конечно не понял что тебе нужно
источник

SK

Sergey Kapralov in JUG NN
Maxim Belov
это конечно интересная инфа, честно говоря не задумывался об этом никогда, но тебе не кажется что ты кэш используешь не для того, для чего он предназаначен? Ну типа задача кэша как ни странно - кэшировать, а пытаешься навернуть на него еще и логику на вычисление одного значения на базе других значений, которые могут быть не инициализированны на этот момент и ловишь дедлок. Может быть просто вынести эту логику непосредственно из кэша, не? Ну т.е. ничего же не мешает заменить первый вариант на второй
def map = new ConcurrentHashMap<String, String>()
map.computeIfAbsent("A", { key ->
   return map.computeIfAbsent("B", {k -> "hello"}) + "ForA"
})

println(map)

// --------

def map2 = new ConcurrentHashMap<String, String>()
def bValue = map2.computeIfAbsent("B", {k -> "hello"})
map2.computeIfAbsent("A", {key -> bValue + "ForA"})

println(map2)
> Может быть просто вынести эту логику непосредственно из кэша, не?

Да говорю же — этот момент понятен) computeIfAbsent — это то, во что я (случайно) впоролся, а не то, куда я иду)

Попозже вернусь вобщем, с более предметным вопросом
источник

MB

Maxim Belov in JUG NN
ну вообще спасибо за отличный пример! тот, что в статье можно хоть на собеседованиях давать и спрашивать "почему так писать нельзя" 😁
источник

SK

Sergey Kapralov in JUG NN
На самом деле я и раньше знал про эту особенность мапы. Просто на практике несложно неочевидно въехать в такое.
источник

SK

Sergey Kapralov in JUG NN
Вобщем: вот так примерно выглядит дилемма: https://gist.github.com/skapral/fa899ce3013b97a8bbfc782c917848e9

Есть B, результат которого зависит от двух A (по определению, эту часть хотелось бы не подвергать переписыванию). И B, и А должны быть мемоизированны при первом вычислении (и должна быть гарантия что вычисление каждого из heavylifting методов происходит не более одного раза). При этом клиентская сторона может потенциально вызвать их в рандомном порядке (сэмулированно в мэйне), и конкуррентно (пока для простоты никак не сэмулированно). Если раннать мейн, он будет виснуть с некоторой вероятностью (если B пойдет вычисляться раньше чем обе A, будет дэдлок)

Далее. Вот так проблему получилось решить (поправьте меня если найдете косяк): https://gist.github.com/skapral/1ff7157647e36aefe4d67c76ec70581b.

Не нравятся две вещи. Синхронайзд на кеше (довольно жирно), и руками писанный даблчек лок. Про лок и был мой вопрос — такое практикуется вообще в 2020м?.
источник

II

Iurii Iurchenko in JUG NN
Maxim Belov
ну вообще спасибо за отличный пример! тот, что в статье можно хоть на собеседованиях давать и спрашивать "почему так писать нельзя" 😁
это вроде не особо очевидно почему так делать нельзя.
внутри метода нет блокирования на каком-то не-реентрант локе. просто алгоритм в этом случае не выходит из одного из циклов в имплементации. надо закапываться чтобы понять что именно не так, но по идее если срабатывает "оптимистичная" блокировка на cas операции совершенно непонятно что мешает перечитать обновлённую корзинку и повторить необходимые проверки и таки засетить значение если ещё можно
источник

SK

Sergey Kapralov in JUG NN
Sergey Kapralov
Вобщем: вот так примерно выглядит дилемма: https://gist.github.com/skapral/fa899ce3013b97a8bbfc782c917848e9

Есть B, результат которого зависит от двух A (по определению, эту часть хотелось бы не подвергать переписыванию). И B, и А должны быть мемоизированны при первом вычислении (и должна быть гарантия что вычисление каждого из heavylifting методов происходит не более одного раза). При этом клиентская сторона может потенциально вызвать их в рандомном порядке (сэмулированно в мэйне), и конкуррентно (пока для простоты никак не сэмулированно). Если раннать мейн, он будет виснуть с некоторой вероятностью (если B пойдет вычисляться раньше чем обе A, будет дэдлок)

Далее. Вот так проблему получилось решить (поправьте меня если найдете косяк): https://gist.github.com/skapral/1ff7157647e36aefe4d67c76ec70581b.

Не нравятся две вещи. Синхронайзд на кеше (довольно жирно), и руками писанный даблчек лок. Про лок и был мой вопрос — такое практикуется вообще в 2020м?.
Any comments? Все настолько плохо что не хочется продолжать?
источник

II

Iurii Iurchenko in JUG NN
public static <T> T memoizedCalculation2(Cache cache, Key key, Callable<T> calculation) {
   Cache.ValueWrapper val = cache.get(key);
   if (val == null) {
       try {
           T newVal = calculation.call();
           Cache.ValueWrapper oldVal = cache.putIfAbsent(key, newVal);
           return oldVal == null ? newVal : (T) oldVal.get();
       } catch (Exception ex) {
           throw new RuntimeException(ex);
       }
   }
   return (T) val.get();
}
источник

SK

Sergey Kapralov in JUG NN
Iurii Iurchenko
public static <T> T memoizedCalculation2(Cache cache, Key key, Callable<T> calculation) {
   Cache.ValueWrapper val = cache.get(key);
   if (val == null) {
       try {
           T newVal = calculation.call();
           Cache.ValueWrapper oldVal = cache.putIfAbsent(key, newVal);
           return oldVal == null ? newVal : (T) oldVal.get();
       } catch (Exception ex) {
           throw new RuntimeException(ex);
       }
   }
   return (T) val.get();
}
Тредсейфети нужон. Здесь — неиллюзорная вероятность того, что calculation.call вызовется дважды.
источник

II

Iurii Iurchenko in JUG NN
да, не успел про это написать. это как вариант. если я не ошибаюсь, то computeIfAbsent тебе тоже гарантии что код вызовется один раз не даёт.
источник

SK

Sergey Kapralov in JUG NN
Iurii Iurchenko
да, не успел про это написать. это как вариант. если я не ошибаюсь, то computeIfAbsent тебе тоже гарантии что код вызовется один раз не даёт.
Вроде, насколько я изучал, зависит от имплементации мапы. Например — для ConcurrentSkipListMapы такой гарантии действительно нет, это прям в джавадоке прописано. А для CHM — есть. Или тоже нет?
источник

II

Iurii Iurchenko in JUG NN
по-моему нет. у тебя есть гарантия что та лямбда которая ты передал в текущем треде вызовется максимум 1 раз
но если у тебя на один ключ два треда конкурентно фиганут по одной и той же лямбде - то гарантии уже нет.
источник

SK

Sergey Kapralov in JUG NN
Iurii Iurchenko
по-моему нет. у тебя есть гарантия что та лямбда которая ты передал в текущем треде вызовется максимум 1 раз
но если у тебя на один ключ два треда конкурентно фиганут по одной и той же лямбде - то гарантии уже нет.
Ну, пусть так. Все равно вариант через компуты не прошел, так какая теперь разница...
источник

II

Iurii Iurchenko in JUG NN
я на 100% за ConcurrentHashMap не уверен если что. может там и есть такая гарантия. ну в любом случае - она тебе нужна?
источник