Лекция 08. Многопоточность и графика ####################################################################################### Потоки =========================================================== Процессы и потоки `````````````````````````````````````````````````````````` **Потоки** и **процессы** - это абстракции управления выполнением программы. * **Процесс** - единица исполнения программы, обладает собственным адресным пространством, служебными структурами. Изолирован в системе от остальных процессов. * **Поток** - единица исполнения внутри процесса. Потоки не обладают адресным пространством, но могут исполняться параллельно друг другу. Каждый процесс имеет хотя бы один выполняющийся поток. Тот поток, с которого начинается выполнение программы, называется главным. В языке Java, после создания процесса, выполнение главного потока начинается с метода main(). Затем, по мере необходимости, в заданных программистом местах, и при выполнении заданных им же условий, запускаются другие, побочные потоки. Модель потока `````````````````````````````````````````````````````````` .. image:: _static/08/threads.png Запуск потоков ----------------------------------------------------------- Запуск потока `````````````````````````````````````````````````````````` Создадим класс от интефейса **Runnable**: .. code-block:: java class SomeThing implements Runnable { //Этот метод будет выполняться в побочном потоке public void run() { System.out.println("Привет из побочного потока!"); } } .. code-block:: java public class Program { static SomeThing sth; public static void main(String[] args) { sth = new SomeThing(); Thread t = new Thread(sth); t.start(); System.out.println("Главный поток завершён..."); } } Результат: .. code-block:: none Главный поток завершён... Привет из побочного потока! .. image:: _static/08/threads2.png Другим вариантом является наследование от **Thread**: .. code-block:: java class MyThread extends Thread { public void run() { System.out.println("Привет из побочного потока!"); } } public class Program { static MyThread t; public static void main(String[] args) { t = new MyThread(); //Создание потока t.start(); //Запуск потока System.out.println("Главный поток завершён..."); } } И совсем короткая реализация: .. code-block:: java public class HelloThread extends Thread { public void run() { System.out.println("Hello from a thread!"); } public static void main(String args[]) { (new HelloThread()).start(); } } Еще один пример: .. code-block:: java class Program { public static void main(String[] args) { Thread t = new Thread (new Runnable() { public void run() { System.out.println("Привет из побочного потока!"); } } } t.start(); } Демоны ----------------------------------------------------------- Модель потока `````````````````````````````````````````````````````````` В Java процесс завершается тогда, когда завершается последний его поток. Даже если метод main() уже завершился, но еще выполняются орожденные им потоки, система будет ждать их завершения. Однако это правило не относится к особому виду потоков – **демонам**. Если завершился последний обычный поток процесса, и остались только потоки-демоны, то они будут принудительно завершены и выполнение процесса закончится. Чаще всего потоки-демоны используются для выполнения фоновых задач, обслуживающих процесс в течение его жизни. Объявить поток демоном достаточно просто — нужно перед запуском потока вызвать его метод **setDaemon(true);** Проверить, является ли поток демоном, можно вызвав его метод **boolean isDaemon()**; Демоны `````````````````````````````````````````````````````````` Рассмотрим пример использования потока-демона: .. code-block:: java class WorkerThread extends Thread { public WorkerThread() { setDaemon(true); } public void run() { int count = 0; while (true) { System.out.println("Hello from Worker "+count++); try { sleep(5000); } catch (InterruptedException e) { // handle exception here } } } } .. code-block:: java public class DaemonTest { public static void main(String[] args) { new WorkerThread().start(); try { Thread.sleep(7500); } catch (InterruptedException e) { // handle here exception } System.out.println("Main Thread ending") ; } } При отсутствии вызова **setDaemon()** вспомогательный поток работает как обычный и завершение главного потока не влияет на его окончание. Если вспомогательный поток запускается в режиме **демона**, он немедленно завершается при завершении главного потока. Управление работой потока ----------------------------------------------------------- Управление потоком `````````````````````````````````````````````````````````` Для управления потоком можно использовать три метода: * **Thread.stop()** - прерывает выполнение. * **Thread.suspend()** - приостанавливает выполнение. * **Thread.resume()** - возобновляет выполнение. * **Thread.sleep()** - ожидание (в мс.) * **Thread.yield()** - принудительный возврат управления. * **Thread.currentThread()** - возвращение родительского потока. Дополнительно: * **join()** - ожидание в потоке завершения другого. * **start()** - начало выполнения. * **setPriority()** - установка приоритета. * **isAlive()** - проверка выполнимости. * **getId()** - возвращение ID-потока В настоящее время методы **stop(),suspend(),resume()** объявлены устаревшими и их использование не поощряется. Вместо них рекомендуется использовать **wait(),notify()** Управление выполнением `````````````````````````````````````````````````````````` Одним из способов управлять потоками является ожидание завершения потомков со стороны родительского потока. Если вызывать в главном потоке **t.join()**, то он будет ожидать завершения работы **t**: .. code-block:: java try { sth = new SomeThing(); Thread t = new Thread(sth); t.start(); t.join(); } catch(InterruptedException ex) { System.out.println("Break!"); } Синхронизация =========================================================== **Синхронизация** используется для согласованной работы нескольких потоков. Необходимость в ней возникает, например, при попытке одновременного доступа к некоторому ресурсу. Если объявить метод как **synchronized**, то при передаче управления, он будет выполнен целиком. Никакой другой поток не сможет вызвать его до окончания работы. **Состояние гонок** - одновременный вызов в потоках одного и того же метода для одного и того же объекта. Существуют 2 основных способа синхронизации: #. Синхронизация метода #. Синхронизация объекта Взаимодействие потоков =========================================================== Взаимодействие потоков осуществляется с помощью методов: * **wait()** - вынуждает вызывающий поток уступить монитор и перейти в состояние ожидания, пока другой поток не вызовет метод **notify()**. * **notify()** - возобновляет исполнение потока, из которого был вызван **wait()**. * **notifyAll()** - возобновляет исполнение всех потоков, из которых был вызван метод **wait()**. Все методы объявлены в классе **Object**. Wait и Notify `````````````````````````````````````````````````````````` Рассмотрим пример с использованием **wait()** и **notify()**. Разработаем класс **Q** (очередь) для управления поставоками и потреблением некоторого ресурса. .. code-block:: java class Q { int n; // номер ресурса boolean valueSet = false; // ресурс не готов synchronized int get() { // получить ресурс while(!valueSet) try { wait(); // ждать } catch(InterruptedException e) { System.out.println("InterruptedException caught"); } System.out.println("Получено: " + n); valueSet = false; notify(); // информировать приостановленный поток return n; } .. code-block:: java synchronized void put(int n) { // добавление ресурса while(valueSet) // пока очередь не пуста, ждем try { wait(); } catch(InterruptedException e) { System.out.println("InterruptedException caught"); } this.n = n; valueSet = true; System.out.println("Добавлено: " + n); notify(); } } .. code-block:: java class Producer implements Runnable { Q q; Producer(Q q) { this.q = q; new Thread(this, "Producer").start(); } public void run() { int i = 0; try { while(true) { Thread.sleep(1000); q.put(i++); // добавляем ресурс в очередь } } catch(InterruptedException ex) { System.out.println("Прервано"); } } } Класс ''Потребитель'' .. code-block:: java class Consumer implements Runnable { Q q; Consumer(Q q) { this.q = q; new Thread(this, "Consumer").start(); } public void run() { while(true) { q.get(); } } } Главный класс программы: .. code-block:: java class PCFixed { public static void main(String args[]) { Q q = new Q(); new Producer(q); new Consumer(q); System.out.println("Press Control-C to stop."); } } Результат выполнения программы: .. image:: _static/08/queue.png Вопросы для самоконтроля =============================================