— Привіт, Аміго! Пам'ятаєш, Еллі розповідала тобі про проблеми під час одночасного доступу кількох потоків до загального (того, що поділяється) ресурсу?
— Так.
— Так ось – це ще не все. Є ще невеличка проблема.
Як ти знаєш, у комп'ютера є пам'ять, де зберігаються данні і команди (код), а також процесор, який виконує ці команди і працює з данними. Процесор зчитує данні з пам'яті, змінює і записує їх назад у пам'ять. Щоб прискорити роботу процесора, в нього вбудували «швидку» пам'ять – кеш.
Щоб прискорити свою роботу, процесор копіює найбільш використовувані змінні з області пам'яті у свій кеш, і всі зміни з ними здійснює у цій швидкій пам'яті. А після – копіює назад у «повільну» пам'ять. Повільна пам'ять весь цей час містить старі (!) (тобто не змінені) значення змінних.
І тоді може виникнути проблема. Один потік змінює змінну, таку як isCancel або isInterrupted з прикладу нижче, а другий потік «не бачить» цієї зміни, оскільки вона була виконана у швидкій пам'яті. Це наслідок того, що потоки не мають доступу до кешу один одного. Процесор часто містить кілька незалежних ядер і потоки фізично можуть виконуватись на різних ядрах.
Згадаємо учорашній приклад:
Код | Опис |
---|---|
|
Потік «не знає» про існування інших потоків.
У методі run змінна isCancel під час першого використання буде поміщена до кешу дочірнього потоку. Ця операція еквівалентна коду:
Виклик методу cancel з іншого потоку змінить значення змінної isCancel у звичайній (повільній) пам'яті, але не в кеші інших потоків. |
|
— Вау! А для цієї проблеми теж придумали гарне рішення, як у випадку з synchronized?
— Ти не повіриш!
Спочатку думали відключити роботу з кешем, але потім виявилося, що через це програми працюють у рази повільніше. Тоді вигадали інше рішення.
Було придумано спеціальне ключове слово volatile. Роміщення цього слова перед визначенням змінної забороняло поміщати її значення у кеш. Точніше, не забороняло поміщати у кеш, а просто примусово завжди читало і писало її лише у звичайну (повільну) пам'ять.
Ось як треба виправити наше рішення, щоб все запрацювало як треба:
Код | Опис |
---|---|
|
Через модифікатор volatile читання та запис значення змінної завжди відбуватимуться у звичайній, спільній для всіх потоків, пам'яті. |
|
— І все?
— Так. Просто і гарно.