Полный отказ от ответственности: на самом деле это не домашняя работа, но я пометил ее как таковую, потому что это в основном упражнение для самообучения, а не на самом деле "для работы".
Скажем такЯ хочу написать простой потокобезопасный модульный счетчик на Java.То есть, если модуль M
равен 3, то счетчик должен циклически проходить через 0, 1, 2, 0, 1, 2, …
до бесконечности.
Вот одна попытка:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicModularCounter {
private final AtomicInteger tick = new AtomicInteger();
private final int M;
public AtomicModularCounter(int M) {
this.M = M;
}
public int next() {
return modulo(tick.getAndIncrement(), M);
}
private final static int modulo(int v, int M) {
return ((v % M) + M) % M;
}
}
Мой анализ (который может быть ошибочным)) этого кода заключается в том, что, поскольку он использует AtomicInteger
, он вполне безопасен для потоков даже без какого-либо явного synchronized
метода / блока.
К сожалению, сам «алгоритм» не совсем"работа", потому что когда tick
обернут вокруг Integer.MAX_VALUE
, next()
может вернуть неправильное значение в зависимости от модуля M
.То есть:
System.out.println(Integer.MAX_VALUE + 1 == Integer.MIN_VALUE); // true
System.out.println(modulo(Integer.MAX_VALUE, 3)); // 1
System.out.println(modulo(Integer.MIN_VALUE, 3)); // 1
То есть два вызова на next()
вернут 1, 1
, когда по модулю 3 и tick
обтекает.
Также может быть проблемас next()
получением значений, не соответствующих порядку, например:
- Thread1 вызовы
next()
- Thread2 вызовы
next()
- Thread2 завершает
tick.getAndIncrement()
, возвращает x - Thread1 завершает
tick.getAndIncrement()
, возвращает y= x + 1 (mod M)
Здесь, за исключением вышеупомянутой проблемы обертывания, x и y действительно являются двумя правильными значениями длявернуть для этих двух next()
вызовов, но в зависимости от того, как указано поведение счетчика, можно утверждать, что они вышли из строя.То есть теперь у нас есть (Thread1, y) и (Thread2, x) , но, возможно, действительно следует указать, что (Thread1, x) и (Thread2, y) - это «правильное» поведение.
Таким образом, по определению некоторых слов, AtomicModularCounter
является поточно-безопасным , но не на самом деле atomic .
Итак, вопросы:
- Правильно ли выполнен мой анализ?Если нет, то, пожалуйста, укажите на любые ошибки.
- Используется ли в моем последнем утверждении правильная терминология?Если нет, то каково правильное утверждение?
- Если вышеупомянутые проблемы реальны, то как бы вы их исправили?
- Можете ли вы исправить это, не используя
synchronized
, используя атомарностьиз AtomicInteger
? - Как бы вы написали это так, что
tick
сам по себе контролируется по диапазону по модулю и никогда даже не получает шанса обернуть Integer.MAX_VALUE
? - Мы можем предположить, что
M
по крайней мере на порядок меньше Integer.MAX_VALUE
при необходимости
Приложение
ВотList
аналог "нестандартной" проблемы ".
- Thread1 звонки
add(first)
- Thread2 звонки
add(second)
Теперь, если мы успешно обновили список с добавлением двух элементов, но second
предшествует first
, что в конце, это "потокобезопасно"?
Если это "потокобезопасно", то что это не так?То есть, если мы укажем, что в вышеприведенном сценарии first
всегда должно предшествовать second
, как называется это свойство параллелизма?(Я назвал это «атомарностью», но я не уверен, что это правильная терминология).
Для чего это стоит, каково поведение Collections.synchronizedList
в отношении этого неупорядоченного аспекта?