Как я могу правильно использовать ключевое слово volatile в Java?
Скажем, у меня есть два потока и объект. Один поток назначает объект:
public void assign(MyObject o) {
myObject = o;
}
Другой поток использует объект:
public void use() {
myObject.use();
}
Следует ли объявлять переменную myObject как изменчивую? Я пытаюсь понять, когда использовать volatile, а когда нет, и это меня озадачивает. Возможно ли, что второй поток содержит ссылку на старый объект в своем кеше локальной памяти? Если нет, почему бы и нет?
Большое спасибо.
Ответы
Ответ 1
Я пытаюсь понять, когда использовать летучий, а когда не
Вам следует избегать его использования. Вместо этого используйте AtomicReference (или другое atomic класс, где это необходимо). Эффекты памяти одинаковы, а намерение намного яснее.
Я очень рекомендую прочитать превосходную Java Concurrency на практике для лучшего понимания.
Ответ 2
Оставив сложные технические детали, вы можете видеть volatile
меньше или больше как модификатор synchronized
для переменных. Если вы хотите синхронизировать доступ к методам или блокам, вам обычно нравится использовать модификатор synchronized
следующим образом:
public synchronized void doSomething() {}
Если вы хотите "синхронизировать" доступ к переменным, вы хотели бы использовать модификатор volatile
:
private volatile SomeObject variable;
За кулисами они выполняют разные вещи, но эффект один и тот же: изменения сразу видны для следующего потока доступа.
В вашем конкретном случае я не думаю, что модификатор volatile
имеет любое значение. volatile
не гарантирует, что поток, назначающий объект, будет выполняться перед потоком с использованием объекта. Это может быть так же хорошо. Вероятно, вы просто хотите сначала выполнить nullcheck в методе use()
.
Обновить: также см. в этой статье:
Доступ к переменной действует как бы, заключен в синхронизированный блок, синхронизированный сам по себе. Мы говорим, что "действует как бы" во втором пункте, потому что для программиста, по крайней мере (и, вероятно, в большинстве реализаций JVM), не задействован фактический объект блокировки.
Ответ 3
Объявление изменчивой переменной Java означает:
- Значение этой переменной никогда не будет кэшировано thread-local
- Доступ к переменной действует так, как если бы она была заключена в синхронизированный блок
Типичное и наиболее распространенное использование летучих:
public class StoppableThread extends Thread {
private volatile boolean stop = false;
public void run() {
while (!stop) {
// do work
}
}
public void stopWork() {
stop = true;
}
}
Ответ 4
В этом случае вы можете использовать volatile. Вам понадобится волатильность, синхронизация вокруг доступа к переменной или некоторому подобному механизму (например, AtomicReference), чтобы гарантировать, что изменения, сделанные в потоке присваивания, действительно видны для потока чтения.
Ответ 5
Я потратил довольно много времени на понимание ключевого слова volatile
.
Я думаю, что @aleroot предоставил лучший и самый простой пример в мире.
Это в свою очередь мое объяснение для манекенов (например, я:-)):
Сценарий1: Предполагая, что stop
не объявляется изменчивым, тогда
данный поток делает и "думает" следующее:
-
stopWork()
: Мне нужно установить stop
в true
- Отлично, я сделал это в своем локальном стеке, теперь мне нужно обновить основную кучу JVM.
- Упс, JVM говорит мне дать путь в CPU к другому потоку, я должен остановиться на некоторое время...
- Хорошо, я вернулся. Теперь я могу обновить основную кучу с моей стоимостью. Обновление...
Сценарий2: Теперь пусть stop
будет объявлен как изменчивый:
-
stopWork()
: Мне нужно установить stop
в true
- Отлично, я сделал это в своем локальном стеке, теперь мне нужно обновить основную кучу JVM.
- Извините, ребята, я должен сделать (2) СЕЙЧАС - мне сказали, что это
volatile
. Я должен занять процессор немного дольше...
- Обновление основной кучи...
- Хорошо, все готово. Теперь я могу уступить.
Нет синхронизации, просто простая идея...
Почему бы не объявить все переменные volatile
на всякий случай? Из-за Scenario2/Step3. Это немного неэффективно, но все же лучше обычной синхронизации.
Ответ 6
Здесь есть некоторые путаные комментарии: уточнить, ваш код некорректен, поскольку два разных потока вызывают assign()
и use()
.
В отсутствие volatile
или другой происходит до отношения (например, синхронизация по общей блокировке) любая запись в myObject
в assign()
не гарантируется видимым потоком, вызывающим use()
- не сразу, не своевременно, а действительно никогда.
Да, volatile
- это один из способов исправить это (если это неправильное поведение - есть вероятные ситуации, в которых вас это не волнует!).
Вы точно верны, что поток "use" может видеть любое "кэшированное" значение myObject
, включая тот, который был назначен во время построения, и любое промежуточное значение (опять же, в отсутствие других событий - перед точками).