Потоковая реализация max
Мне нужно реализовать глобальную статистику сбора объектов для веб-сервера. У меня есть Statistics
singleton, у которого есть метод addSample(long sample)
, который впоследствии вызывает updateMax
. Это должно быть явно потокобезопасным. У меня есть этот метод для обновления максимум всей статистики:
AtomicLong max;
private void updateMax(long sample) {
while (true) {
long curMax = max.get();
if (curMax < sample) {
boolean result = max.compareAndSet(curMax, sample);
if (result) break;
} else {
break;
}
}
}
Правильно ли эта реализация? Я использую java.util.concurrent, потому что считаю, что это будет быстрее простого synchronized
. Есть ли другой/лучший способ реализовать это?
Ответы
Ответ 1
Я думаю, что это правильно, но я бы, вероятно, немного переписал его для ясности и, безусловно, добавил комментарии:
private void updateMax(long sample) {
while (true) {
long curMax = max.get();
if (curMax >= sample) {
// Current max is higher, so whatever other threads are
// doing, our current sample can't change max.
break;
}
// Try updating the max value, but only if it equal to the
// one we've just seen. We don't want to overwrite a potentially
// higher value which has been set since our "get" call.
boolean setSuccessful = max.compareAndSet(curMax, sample);
if (setSuccessful) {
// We managed to update the max value; no other threads
// got in there first. We're definitely done.
break;
}
// Another thread updated the max value between our get and
// compareAndSet calls. Our sample can still be higher than the
// new value though - go round and try again.
}
}
EDIT: Обычно я, по крайней мере, сначала пробовал синхронизированную версию и только для этого типа кода без блокировки, когда обнаружил, что это вызывает проблему.
Ответ 2
Я считаю, что вы сделали правильно, но это более простая версия, которая, как я думаю, верна.
private void updateMax(long sample){
//this takes care of the case where between the comparison and update steps, another thread updates the max
//For example:
//if the max value is set to a higher max value than the current value in between the comparison and update step
//sample will be the higher value from the other thread
//this means that the sample will now be higher than the current highest (as we just set it to the value passed into this function)
//on the next iteration of the while loop, we will update max to match the true max value
//we will then fail the while loop check, and be done with trying to update.
while(sample > max.get()){
sample = max.getAndSet(sample);
}
}
Ответ 3
как будто у вас не было выбора ответов, здесь моя:
// while the update appears bigger than the atomic, try to update the atomic.
private void max(AtomicDouble atomicDouble, double update) {
double expect = atomicDouble.get();
while (update > expect) {
atomicDouble.weakCompareAndSet(expect, update);
expect = atomicDouble.get();
}
}
он более или менее совпадает с принятым ответом, но не использует break
или while(true)
, который мне лично не нравится.
РЕДАКТИРОВАТЬ: только что обнаружил DoubleAccumulator
в java 8. документация даже говорит, что это для проблем с суммарной статистикой, таких как ваши:
DoubleAccumulator max = new DoubleAccumulator(Double::max, Double.NEGATIVE_INFINITY);
parallelStream.forEach(max::accumulate);
max.get();
Ответ 4
С помощью java 8 вы можете использовать функциональные интерфейсы и простое выражение lamda для решения этой проблемы с одной строкой и без цикла:
private void updateMax(long sample) {
max.updateAndGet(curMax -> (sample > curMax) ? sample : curMax);
}
В решении используется метод updateAndGet (LongUnaryOperator). Текущее значение содержится в curMax и с использованием условного оператора выполняется простой тест, заменяющий текущее максимальное значение значением образца, если значение выборки больше текущего максимального значения.