LongAdder Striped64 был неконтролируемая деталь реализации
Это вопрос не о том, как работает LongAdder, а о интригующей детализации, которую я не могу понять.
Вот код из Striped64 (я вырезал некоторые части и оставил соответствующие части для вопроса):
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
ThreadLocalRandom.current(); // force initialization
h = getProbe();
wasUncontended = true;
}
boolean collide = false; // True if last slot nonempty
for (;;) {
Cell[] as; Cell a; int n; long v;
if ((as = cells) != null && (n = as.length) > 0) {
if ((a = as[(n - 1) & h]) == null) {
//logic to insert the Cell in the array
}
// CAS already known to fail
else if (!wasUncontended) {
wasUncontended = true; // Continue after rehash
}
else if (a.cas(v = a.value, ((fn == null) ? v + x : fn.applyAsLong(v, x)))){
break;
}
Мне многое понятно из кода, кроме:
// CAS already known to fail
else if (!wasUncontended) {
wasUncontended = true; // Continue after rehash
}
Откуда такая уверенность, что следующий CAS потерпит неудачу? По крайней мере, это действительно сбивает меня с толку, потому что эта проверка имеет смысл только для одного случая: когда какой-то поток входит в метод longAccumulate в n-й раз (n > 1), и вращение занято в его первом цикле.
Это похоже на код, который говорит: если вы (какой-то поток) были здесь раньше, и у вас есть некоторая конкуренция в конкретном слоте ячейки, не пытайтесь CAS ваше значение к уже существующему, а вместо этого перефразируйте зонд.
Я искренне надеюсь, что у меня будет смысл для кого-то.
1 ответ
Дело не в том, что оно провалится, а в том, что оно провалилось. Вызов этого метода выполняется LongAdder
add
метод.
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
- Первый набор условий связан с существованием длинных ячеек. Если нужной ячейки не существует, она будет пытаться накапливать непреднамеренное (как не было попытки добавить) путем атомарного добавления необходимой ячейки и последующего добавления.
- Если ячейка существует, попробуйте добавить (
v + x
). Если при добавлении произошел сбой, возникла некоторая форма разногласий, в этом случае попытайтесь выполнить накопление оптимистично / атомарно (вращение до успешного завершения)
Так почему это имеет
wasUncontended = true; // Continue after rehash
Мое лучшее предположение состоит в том, что при сильном конфликте он попытается дать время работающему потоку наверстать упущенное и принудительно повторить существующие ячейки.