"> 4. Проблеми при реалізації паралельних програм
Наслідком збільшеною складності паралельних програм є велика кількість можливих проблем. Розглянемо деякі з них.
4.1 Взаємне блокування (deadlock)
Виникає коли кілька потоків при спроби синхронізації по декількох точках блокуються в очікуванні одного друга. Наприклад один потік володіє однією точкою синхронізації, другий другий і кожен з них очікує захоплення точки яка вже захоплена. Це відбувається через неконтрольованого порядку захоплення точок синхронізації, що не завжди можливо у великому додатку. Дана проблема має кілька способів вирішення: автоматичне детектування взаємного блокування та вжиття заходів щодо її усунення, захоплювати точку синхронізації без постійної блокування механізму захоплення, жорстко гарантувати порядок захоплення. Потрібно враховувати, що ця проблема характерна для будь-якого механізму синхронізації який може блокувати поточний потік назавжди. Розглянемо найпростіший приклад взаємного блокування.
Приклад 10.
public class DeadLock {static void main (String [] args) {ReentrantLock lock1=new ReentrantLock (); ReentrantLock lock2=new ReentrantLock (); t=new Thread (new Runnable () {
@ Overridevoid run () {.lock (); () ;. lock (); {
//критична секція
} {. unlock () ;. unlock ();
}
}
}, slave ) ;. start () ;. lock (); ();
lock1.lock (); {
//критична секція
}
finally {.unlock () ;. unlock ();
}. out.println ( finished );
}
/**
* Затримка на 1 секунду
*/static void delay () {{.sleep (1000);
} catch (InterruptedException e) {
e.printStackTrace ();
}
}
}
Виконання коду представленого вище ніколи не призведе до висновку на екран рядки finished raquo ;. Потоки main і slave будуть заблоковані назавжди після того, як перший захопить lock1, а другий lock2. У такому маленькому фрагменті коду не викличе проблем детектувати взаємне блокування просто подивившись лістинг. У реальності не завжди виходить це зробити на етапі розробки (хоча можливо використовувати статичний аналізатор коду), тому потрібні методи детектування таких ситуацій під час виконання. Найбільш зручним методом є вивчення дампа потоків, який можна отримати за допомогою утиліти jstack, або пославши сигнал виконуваних процесів (для консольного windows додатки Ctrl + Break, в linux сигнал SIGQUIT). Для нашого прикладу частина дампа потоків буде виглядати подібно представленому нижче.
Приклад 11.
one Java-level deadlock:
=============================
slave : for ownable synchronizer 0x2299c870,
(a java.util.concurrent.locks.ReentrantLock $ NonfairSync), is held by main
main : for ownable synchronizer 0x2299c848,
(a java.util.concurrent.locks.ReentrantLock $ NonfairSync), is held by slave stack information for the threads listed above:
========================================== =========
slave : sun.misc.Unsafe.park (Native Method)
parking to wait for lt; 0x2299c870 gt;
(a java.util.concurrent.locks.ReentrantLock$NonfairSync)java.util.concurrent.locks.LockSupport.park(Unknown Source)
... опущена частина виведення несуттєва в контексті обговорення
at java.lang.Threadn (Unknown Source)
main : sun.misc.Unsafe.park (Native Method)
parking to wait for lt; 0x2299c848 gt;
(a java.util.concurrent.locks.ReentrantLock$NonfairSync)java.util.concurrent.locks.LockSupport.park(Unknown Source)
... опущена частина виведення несуттєва в контексті обговорення
at kz.pnhz.test.sandbox.DeadLock.main (DeadLock.java:31)
Found 1 deadlock.
... опущена частина виведення несуттєва в контексті обговорення
Таким чином можливо з легкістю розпізнати причину взаємного блокування. Хоча deadlock часто виявляється під час виконання з причини виникнення це помилка проектування або реалізації програми та повинна виправлятися на етапі розробки.