Page 253 - 6253
P. 253
одночасно намагаються виконати наступну операцію:
accounts[to] = accounts[to] + amount;
Проте дана операція не є атомарною і може бути виконана поетапно наступним
чином:
1) завантажити значення елемента accounts[to]з масиву в регістр;
2) додати до нього значення змінної amount;
3) записати результат назад в масив на місце елемента accounts[to].
Представимо, що в першому потоці виконуються операції 1 та 2, після чого
його виконання призупиняється. Припустимо, що другий потік виходить із стану
очікування і оновлює той же самий елемент у масиві accounts. Потім виходить із
стану очікування перший потік і виконує операцію під номером 3. Дана дія затирає
зміни, що бути зроблені в другому потоці. У підсумку загальний баланс виявляється
підрахованим невірно.
Суть розглянутої проблеми полягає в тому, що виконання методу transfer()
може бути перервано на півдорозі до завершення його виконання. Для того щоб стан
об’єкта банківського рахунку не був порушений, потрібно гарантувати нормальне
завершення методу transfer() до того, як його потік втратить керування.
10.4.3 Використання об’єктів блокування
У мові Java існує два механізми для захисту критичної секції коду від
паралельного доступу. Один із них полягає у використанні ключового слова
synchronized, а інший – у використанні класу ReentrantLock, що з’явився у версії
JavaSE 5.0. Ключове слово synchronized автоматично забезпечує блокування, як і
пов’язана з ним умова, яку зручно використовувати в більшості випадків, коли
необхідно реалізувати явне блокування.
Захист блоку коду засобами класу ReentrantLock в загальному випадку виглядає
наступним чином:
myLock.lock(); // об’єкт типу ReentrantLock
try {
критична секція коду;
} finally {
// зняти блокування, навіть у випадку генерації винятку
252