— Привіт, Аміго!
— Привіт, Еллі!
— Хочу розповісти тобі про модифікатора 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 наше все.
— Ясно.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ