— Привет, Амиго!
— Привет, Элли!
— Хочу рассказать тебе о модификаторе volatile. Знаешь, что это такое?
— Вроде что-то связанное с нитями. Не помню точно.
— Тогда слушай. Вот тебе немного технических деталей:
В компьютере есть два вида памяти – глобальная (обычная) и встроенная в процессор. Встроенная в процессор делится на регистры, затем кэш первого уровня (L1), кэш второго уровня (L2) и третьего уровня (L3).
Эти виды памяти отличаются по скорости работы. Самая быстрая и самая маленькая память – это регистры, затем идет кэш процессора (L1, L2, L3) и, наконец, глобальная память (самая медленная).
Скорость работы глобальной памяти и кэша процессора сильно отличаются, поэтому Java-машина позволяет каждой нити хранить самые часто используемые переменные в локальной памяти нити (в кэше процессора).
— А можно как-то управлять этим процессом?
— Практически никак – всю работу делает Java-машина, она очень интеллектуальная в плане оптимизации скорости работы.
Но я тебе это рассказываю вот зачем. Есть одна маленькая проблемка. Когда две нити работают с одной и той же переменной, каждая из них может сохранить ее копию в своем внутреннем локальном кэше. И тогда может получится такая ситуация, что одна нить переменную меняет, а вторая не видит этого изменения, т.к. по-прежнему работает со своей копией переменной.
— И что же делать?
— На этот случай разработчики Java предусмотрели специальное ключевое слово – volatile. Если есть переменная, к которой обращаются из разных нитей, ее нужно пометить модификатором volatile, чтобы Java-машина не помещала ее в кэш. Вот как это обычно выглядит:
public volatile int count = 0;
— О, вспомнил. Ты же уже про это рассказывала. Я же это уже знаю.
— Ага, знаешь. А вспомнил, только когда я рассказала.
— Э, ну забыл немного.
— Повторение – мать учения.
Вот тебе несколько новых фактов работы модификатора volatile. Модификатор volatile гарантирует только безопасное чтение/запись переменной, но не ее изменение.
— А в чем разница?
— Вот смотри. Как изменяется переменная:
Код | Что происходит на самом деле: | Описание |
---|---|---|
|
|
Этап 1. Значение переменной count копируется из глобальной памяти в регистровую память процессора. Этап 2 Этап 3 |
— Ого! Так что, изменение любой переменной происходит только в процессоре?
— Ага.
— И значения копируются туда-сюда: из памяти в процессор и обратно?
— Ага.
Так вот, модификатор volatile, гарантирует, что при обращении к переменной count она будет прочитана из памяти (этап 1). А если какая-то нить захочет присвоить ей новое значение, то оно обязательно окажется в глобальной памяти (этап 3).
Но Java-машина не гарантирует, что не будет переключения нитей между этапами 1 и 3.
— Т.е. увеличение переменной на 1 – это фактически три операции?
— Да.
— И если две нити одновременно захотят исполнить count++, то они могут помешать друг другу?
— Да, вот смотри:
Нить 1 | Нить 2 | Результат |
---|---|---|
|
|
|
— Т.е. обращаться к переменной можно, а изменять рискованно все равно?
— Ну, изменять можно, только осторожно ☺
— Это как же?
— synchronized наше все.
— Ясно.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ