русс | укр

Мови програмуванняВідео уроки php mysqlПаскальСіАсемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование


Linux Unix Алгоритмічні мови Архітектура мікроконтролерів Введення в розробку розподілених інформаційних систем Дискретна математика Інформаційне обслуговування користувачів Інформація та моделювання в управлінні виробництвом Комп'ютерна графіка Лекції


Тема 12 Синхронізація


Дата додавання: 2014-05-29; переглядів: 937.


План

1 Синхронізація

2 Зберігання змінних у пам’яті

3 Блокування

 

1 Синхронізація

При багатопоточній архітектурі додатка можливі ситуації, коли кілька потоків будуть одночасно працювати з тими самими даними, використовуючи їхнього значення й привласнюючи нові. У такому випадку результат роботи програми стає неможливо вгадати, дивлячись тільки на вихідний код. Фінальні значення змінних будуть залежати від випадкових факторів, виходячи з того, який потік яку дію встиг зробити першим або останнім.

Для більше глибокого розуміння принципів багатопоточної роботи та синхроніхації в Java розглянемо організацію пам'яті у віртуальній машині для декількох потоків.

 

2 Зберігання змінних у пам'яті

Віртуальна машина підтримує основне сховище даних (main storage), у якому зберігаються значення всіх змінних і котре використовується всіма потоками. Під змінними тут розуміються поля об'єктів і класів, а також елементи масивів. Що стосується локальних змінних і параметрів методів, те їхні значення не можуть бути доступні іншим потокам, тому вони не представляють інтересу.

Для кожного потоку створюється його власна робоча пам'ять (working memory), у яку перед використанням копіюються значення всіх змінних.

Розглянемо основні операції, доступні для потоків при роботі з пам'яттю:

- use - читання значення змінної з робочої пам'яті потоку;

- assign - запис значення змінної в робочу пам'ять потоку;

- read - одержання значення змінної з основного сховища;

- load - збереження значення змінної, прочитаного з основного сховища, у робочій пам'яті;

- store - передача значення змінної з робочої пам'яті в основне сховище для подальшого зберігання;

- write - зберігає в основному сховищі значення змінною, переданою командою store.

Підкреслимо, що перераховані команди не є методами яких-небудь класів, вони недоступні програмістові. Сама віртуальна машина використовує їх для забезпечення коректної роботи потоків виконання.

Потік, працюючи зі змінної, регулярно застосовує команди use і assign для використання її поточного значення й присвоєння нового. Крім того, повинні здійснюватися дії по передачі значень в основне сховище й з нього. Вони виконуються у два етапи. При одержанні даних спочатку основне сховище зчитує значення командою read, а потім потік зберігає результат у своїй робочій пам'яті командою load. Ця пара команд завжди виконується разом саме в такому порядку, тобто не можна виконати одну, не виконавши іншу. При відправленні даних спочатку потік зчитує значення з робочої пам'яті командою store, а потім основне сховище зберігає його командою write. Ця пара команд також завжди виконується разом саме в такому порядку, тобто не можна виконати одну, не виконавши іншу.

Набір цих правил складався для того, щоб операції з пам'яттю були досить строгі для точного аналізу їхніх результатів, а з іншого боку, правила повинні залишати достатній простір для різних технологій оптимизаций (регістри, черги, кэш і т.д.).

За винятком деяких додаткових очевидних правил, більше ніяких обмежень немає. Наприклад, якщо потік змінив значення спочатку однієї, а потім інший змінної, те ці зміни можуть бути передані в основне сховище у зворотному порядку.

Потік створюється із чистою робочою пам'яттю й повинен перед використанням завантажити всі необхідні змінні з основного сховища. Будь-яка змінна спочатку створюється в основному сховищі й лише потім копіюється в робочу пам'ять потоків, які будуть неї застосовувати.

Таким чином, потоки ніколи не взаємодіють один з одним прямо, тільки через головне сховище.

 

3 Блокування

В основному сховищі для кожного об'єкта підтримується блокування (lock), над якою можна зробити дві дії - установити (lock) і зняти (unlock). Тільки один потік в один момент часу може встановити блокування на деякий об'єкт. Якщо до того, як цей потік виконає операцію unlock, інший потік спробує встановити блокування, його виконання буде припинено доти, поки перший потік не відпустить її.

Операції lock і unlock накладають тверде обмеження на роботу зі змінними в робочій пам'яті потоку. Після успішно виконаного lock робоча пам'ять очищається й вес змінні необхідно заново зчитувати з основного сховища. Аналогічно, перед операцією unlock необхідно всі змінні зберегти в основному сховищі.

Важливо підкреслити, що блокування є чимсь начебто прапора. Якщо блокування на об'єкт установлена, це не означає, що даним об'єктом не можна користуватися, що його поля й методи стають недоступними,- це не так. Єдина дія, що стає неможливим,- установка цього ж блокування іншим потоком, доти, поки перший потік не виконає unlock.

В Java-програмі для того, щоб скористатися механізмом блокувань, існує ключове слово synchronized. Воно може бути застосоване у двох варіантах - для оголошення synchronized-блоку і як модифікатор методу. В обох випадках дія його приблизно однакове.

Synchronized-Блок записується в такий спосіб:

 

synchronized (ref) {

...

}

 

Перш, ніж почати виконувати дії, описані в цьому блоці, потік зобов'язаний установити блокування на об'єкт, на який посилається змінна ref (тому вона не може бути null). Якщо інший потік уже встановив блокування на цей об'єкт, то виконання першого потоку припиняється доти, поки не вдасться виконати операцію lock.

Після цього блок виконується. При завершенні виконання (як успішному, так і у випадку помилок) виробляється операція unlock, щоб звільнити об'єкт для інших потоків.

Synchronized-методи працюють аналогічним чином. Перш, ніж почати виконувати їх, потік намагається заблокувати об'єкт, у якого викликається метод. Після виконання блокування знімається. У попередньому прикладі аналогічної впорядкованості можна було домогтися, якщо використовувати не synchronized-блок, а оголосити метод process() синхронізованим.

Також припустимі методи static synchronized. При їхньому виклику блокування встановлюється на об'єкт класу Class, відповідального за тип, у якого викликається цей метод.

При роботі із блокуваннями завжди треба пам'ятати про можливість появи deadlock - взаємних блокувань, які приводять до зависання програми. Якщо один потік заблокував один ресурс і намагається заблокувати другий, а інший потік заблокував другий і намагається заблокувати перший, то такі потоки вже ніколи не вийдуть зі стану очікування.

В Java немає ніяких засобів розпізнавання або запобігання ситуацій deadlock. Також немає способу перед викликом синхронізованого методу довідатися, чи заблокований уже об'єкт іншим потоком. Програміст сам повинен планувати роботу програми таким чином, щоб нерозв'язні блокування не виникали.

Небезпека виникнення взаємних блокувань змушує з особливою увагою ставитися до роботи з потоками. Наприклад, важливо пам'ятати, що якщо в об'єкта потоку був викликаний метод sleep(..), те такий потік буде не діяти певний час, але при цьому всі заблоковані їм об'єкти будуть залишатися недоступними для блокувань із боку інших потоків, а це потенційний deadlock. Такі ситуації вкрай складно виявити шляхом тестування й налагодження, тому питанням синхронізації треба приділяти багато часу на етапі проектування.


 


<== попередня лекція | наступна лекція ==>
Робота із пріоритетами | Тема 13 Підключення зовнішніх бібліотек


Онлайн система числення Калькулятор онлайн звичайний Науковий калькулятор онлайн