Page 142 - 4868
P. 142

Ошибка! Стиль не определен.                                                              140

               на будь-якому банківському рахунку в довільний момент часу. Але в той же
               час відомо, що загальна сума грошей за всіма рахунками повинна залишатися
               незмінною, оскільки гроші тільки переводяться з одного рахунку на інший,
               але  не  знімаються  остаточно.  Наприкінці  кожної  транзакції  метод
               transfer() заново обчислює  кінцеву  суму на рахунках  і  виводить  її.  При
               цьому програма працює у вічному циклі.
                     Запустивши  програму  на  виконання,  можна  виявити,  що  через  деякий
               час загальний баланс все-таки зміниться. Це пов’язано з тим, що два потоки
               намагаються одночасно оновити один і той же рахунок. Припустимо, що два
               потоки одночасно намагаються виконати наступну операцію:

                     accounts[to] = accounts[to] + amount;

                     Проте  дана  операція  не  є  атомарною  і  може  бути  виконана  поетапно
               наступним чином:
                     1) завантажити значення елемента accounts[to]з масиву в регістр;
                     2) додати до нього значення змінної amount;
                     3) записати результат назад в масив на місце елемента accounts[to].
                     Представимо, що в першому потоці виконуються операції 1 та 2, після
               чого його виконання призупиняється. Припустимо, що другий потік виходить
               із  стану  очікування  і  оновлює  той  же  самий  елемент  у  масиві  accounts.
               Потім  виходить  із  стану  очікування  перший  потік  і  виконує  операцію  під
               номером  3.  Дана  дія  затирає  зміни,  що  бути  зроблені  в  другому  потоці.  У
               підсумку загальний баланс виявляється підрахованим невірно.
                     Суть  розглянутої  проблеми  полягає  в  тому,  що  виконання  методу
               transfer()  може  бути  перервано  на  півдорозі  до  завершення  його
               виконання.  Для  того,  щоб  стан  об’єкта  банківського  рахунку  не  був
               порушений,        потрібно       гарантувати       нормальне       завершення        методу
               transfer() до того, як його потік втратить керування.


                     19.3. Використання об’єктів блокування

                     У мові Java  існує два механізми для захисту критичної секції коду від
               паралельного доступу. Один із них полягає у використанні ключового слова
               synchronized,  а  інший  –  у  використанні  класу  ReentrantLock,  що
               з’явився  у  версії  JavaSE 5.0.  Ключове  слово  synchronized  автоматично
               забезпечує  блокування,  як  і  пов’язана  з  ним  умова,  яку  зручно
               використовувати  в  більшості  випадків,  коли  необхідно  реалізувати  явне
               блокування.
                     Захист  блоку  коду  засобами  класу  ReentrantLock  в  загальному
               випадку виглядає наступним чином:

                     myLock.lock(); // об’єкт типу ReentrantLock
                     try {
                       критична секція коду;
                     } finally {
                       // зняти блокування, навіть у випадку генерації винятку
                       myLock.unlock();
   137   138   139   140   141   142   143   144   145   146   147