Я прочитал, что вы должны использовать volatile, когда поток обращается к переменной, чтобы убедиться, что потоки видят правильное значение, но применимо ли это, когда переменная используется в методе?
В некоторых отношениях это неточно *.
Во-первых: volatile
можно использовать только для полей. Его нельзя использовать с другими видами переменных; т.е. локальные переменные или параметры. (Причина: это было бы бессмысленно, потому что другие виды переменных видны только одному потоку.)
Во-вторых: volatile
семантика применяется независимо от того, вызываете ли вы метод или нет.
В-третьих: volatile
- это альтернатива использованию synchronized
методов (или блоков). Но правильное использование synchronized
обычно зависит от всех обращений и обновлений, выполняемых с использованием конструкции синхронизации. Любые несинхронизированные обращения или обновления могут сделать программу (в целом) некорректной.
Наконец: volatile
означает, что доступ к полю будет видеть последнее значение значения поля. Этого недостаточно для гарантии правильности . Например:
public Counter {
private volatile int count;
public void increment() {
// This is NOT correct.
count++;
}
}
Причина неправильности приведенного выше примера в том, что count++
не является атомарным. На самом деле это означает (буквально) count = count + 1
, и другой поток может изменить count
между текущим потоком, обращающимся к нему и записывающим обновленное значение. Это может привести к случайным потерям приращений.
Легко видеть, что версия вашего кода, которая объявила max
как volatile
и избавилась от ключевого слова synchronized
, была бы неправильной по той же самой причине. Ваша текущая версия более верна в этом отношении. Однако это все еще не совсем правильно, потому что ничто не мешает другому классу (в том же пакете) получить доступ или обновить max
. Если бы это было сделано в другом потоке, у вас была бы потенциальная проблема параллелизма ... в дополнение к дырявой абстракции.
Итак, чтобы ответить на ваши вопросы:
Безопасно ли предполагать, что если несколько потоков вызывают doSomething, то max будет установлено в то же состояние, в котором предыдущий поток вызывал метод?
Не совсем, из-за утечки абстракции. (Объявление max
как приватного исправляет это.)
Поскольку doSomething () синхронизирован, я знаю, что только один поток может изменить max, но что происходит, когда следующий поток вызывает его?
Блок synchronized
гарантирует, что следующий поток, который синхронизируется с тем же объектом, увидит эти обновления. (Как это достигается, зависит от платформы ...)
Может ли max быть другим значением, потому что оно не использует volatile?
Нет ... по модулю проблемы утечки абстракции.
Или это безопасно, потому что экземпляр Limiter изменяет его сам?
Этот факт не имеет отношения к безопасности потока / правильности этого кода. Важна синхронизация, а не самомодификация.
* По крайней мере, ваше краткое изложение того, что вы поняли по прочитанному, является неточным. Насколько нам известно, исходный текст, который вы прочитали, может быть верным.
UPDATE
В части, которую я прочитал, речь шла о полях (как в переменных, доступных потоку). Извините за путаницу. То, что я хотел знать, было ли это также применимо ко всем переменным, используемым методом в объекте, вызываемом потоком.
Это относится ко всем полям объекта, которые поток читает и / или пишет, покрыты синхронизированной областью. И действительно, это относится к полям других объектов, а также к элементам массивов, считываемых / записываемых в регионе. (Это «спорный» для переменных, которые не являются полями, потому что никакой другой поток не может их видеть.)
Однако предостережение заключается в том, что оно применяется ТОЛЬКО к точке синхронизации. Если второй поток не синхронизируется должным образом, все ставки отключены.