Нужно ли «изменчивое» в методах? - PullRequest
2 голосов
/ 11 марта 2012

Я прочитал, что вы должны использовать volatile, когда поток обращается к переменной, чтобы убедиться, что потоки видят правильное значение, но применимо ли это, когда переменная используется в методе?

Пример кода:

public class Limiter
{
    int max = 0;

    public synchronized void doSomething()
    {
         max++;
         if(max < 10)
             System.out.println("Work");

         System.out.println(max);
    }

}

Безопасно ли предполагать, что если несколько потоков вызывают doSomething, тогда max будет установлен в то же состояние, в котором предыдущий поток вызывал метод?

Поскольку doSomething() синхронизирован, я знаю, что только один поток может изменить max, но что происходит, когда следующий поток вызывает его? Может ли max быть другим значением, потому что оно не использует volatile? Или это безопасно, потому что экземпляр Limiter изменяет его сам?

Ответы [ 2 ]

7 голосов
/ 11 марта 2012

volatile является частью объявления поля, а не частью его использования . Не имеет смысла объявлять локальную переменную как volatile, поскольку локальные переменные не будут видны различным потокам.

В вашем случае у вас все в порядке, если нет доступа к коду в несинхронизированных методах max - модель памяти в основном гарантирует, что, пока весь код получает / выпускает один и тот же монитор, "охраняя" переменную, все потоки будут видеть последовательную последовательность значений. (Помимо всего прочего, тот факт, что каждый из потоков должен получить монитор перед доступом к значению, означает, что только один поток сможет получить доступ к значению за раз - вы могли бы записать полный порядок: «поток X имел монитор на время t0-t1, поток Y имел монитор во время t4-t5 "и т. д.)

2 голосов
/ 11 марта 2012

Я прочитал, что вы должны использовать 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

В части, которую я прочитал, речь шла о полях (как в переменных, доступных потоку). Извините за путаницу. То, что я хотел знать, было ли это также применимо ко всем переменным, используемым методом в объекте, вызываемом потоком.

Это относится ко всем полям объекта, которые поток читает и / или пишет, покрыты синхронизированной областью. И действительно, это относится к полям других объектов, а также к элементам массивов, считываемых / записываемых в регионе. (Это «спорный» для переменных, которые не являются полями, потому что никакой другой поток не может их видеть.)

Однако предостережение заключается в том, что оно применяется ТОЛЬКО к точке синхронизации. Если второй поток не синхронизируется должным образом, все ставки отключены.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...