Page 246 - 6571
P. 246
Під час виконання даної програми, невідомо, яка саме сума
знаходиться на будь-якому банківському рахунку в довільний
момент часу. Але в той же час відомо, що загальна сума грошей
за всіма рахунками повинна залишатися незмінною, оскільки
гроші тільки переводяться з одного рахунку на інший, але не зні-
маються остаточно. Наприкінці кожної транзакції метод tran
fer() заново обчислює кінцеву суму на рахунках і виводить її
на екран.
Запустивши програму на виконання, можна виявити, що че-
рез деякий час загальний баланс все-таки зміниться. Це пов’язано
з тим, що два потоки намагаються одночасно оновити один і той
же рахунок. Припустимо, що два потоки одночасно намагаються
виконати наступну операцію:
account1 = account1 - amount;
Проте дана операція не є атомарною і може бути виконана
поетапно наступним чином:
1) завантажити значення поля account1 в регістр;
2) додати до нього значення змінної amount;
3) записати результат назад у поле account1.
Представимо, що в першому потоці виконуються операції 1
та 2, після чого його виконання призупиняється. Припустимо, що
другий потік виходить із стану очікування і оновлює те ж саме
поле account1. Потім виходить із стану очікування перший по-
тік і виконує операцію під номером 3. Дана дія затирає зміни, що
бути зроблені в другому потоці. У підсумку загальний баланс ви-
являється вирахуваним невірно.
Суть розглянутої проблеми полягає в тому, що виконання
методу transfer() може бути перервано на півдорозі до заве-
ршення його виконання. Для того, щоб стан об’єкта банківського
рахунку не був порушений, потрібно гарантувати нормальне за-
вершення методу transfer() до того, як потік, що його вико-
нує втратить керування.
У мові Java існує два механізми для захисту критичної секції
коду від паралельного доступу. Один із них полягає у
використанні ключового слова synchronized, а інший – у
використанні класу ReentrantLock, який реалізує інтерфейс
Lock що з’явився у версії JavaSE 5.0.
245