Page 179 - 6571
P. 179
double amount = maxAmount * Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int) (DELAY * Math.random()));
} catch (InterruptedException e) { }
}
}
Під час виконання даної програми, невідомо, яка саме сума
знаходиться на будь-якому банківському рахунку в довільний
момент часу. Але в той же час відомо, що загальна сума грошей
за всіма рахунками повинна залишатися незмінною, оскільки
гроші тільки переводяться з одного рахунку на інший, але не
знімаються остаточно. Наприкінці кожної транзакції метод
transfer() заново обчислює кінцеву суму на рахунках і
виводить її. При цьому програма працює у вічному циклі.
Запустивши програму на виконання, можна виявити, що
через деякий час загальний баланс все-таки зміниться. Це
пов’язано з тим, що два потоки намагаються одночасно оновити
один і той же рахунок. Припустимо, що два потоки одночасно
намагаються виконати наступну операцію:
accounts[to] = accounts[to] + amount;
Проте дана операція не є атомарною і може бути виконана
поетапно наступним чином:
1) завантажити значення елемента accounts[to]з масиву в
регістр;
2) додати до нього значення змінної amount;
3) записати результат назад в масив на місце елемента
accounts[to].
Представимо, що в першому потоці виконуються операції 1
та 2, після чого його виконання призупиняється. Припустимо, що
другий потік виходить із стану очікування і оновлює той же
самий елемент у масиві accounts. Потім виходить із стану
очікування перший потік і виконує операцію під номером 3. Дана
дія затирає зміни, що бути зроблені в другому потоці. У підсумку
загальний баланс виявляється підрахованим невірно.
Суть розглянутої проблеми полягає в тому, що виконання
методу transfer() може бути перервано на півдорозі до
завершення його виконання. Для того щоб стан об’єкта
банківського рахунку не був порушений, потрібно гарантувати
178