Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
методичка Java-технологии.doc
Скачиваний:
12
Добавлен:
01.05.2019
Размер:
793.09 Кб
Скачать

Контрольні питання.

  • Що таке інтерфейс?

  • У чому основні розходження класів й інтерфейсів?

  • Чи можна створити екземпляр типу інтерфейс?

Завдання.

1. Написати додаток, у якому буде оголошені клас і кілька інтерфейсів. Скомбінуйте їх разом і створіть новий клас.

2.Напишіть додаток, у якому будуть оголошені кілька інтерфейсів. Скомбінуйте їх й успадкуйте новий інтерфейс.

3. Розробити в пакеті додатка бібліотеку класів для ієрархії фігур. Реалізація повинна бути з методами show й hide. Замість показу на екрані ці методи повинні виводити в консольне вікно виводу ім'я класу фігури й слово show або hide, а також координати x й y фігури.

4.Розробити додаток, у якому використаються ці класи - створюється фігура потрібного типу при натисканню на кнопку й “показується”.

Лабораторна робота № 5.

Тема: Потоки виконання й синхронізація.

Ціль: Навчитися використовувати функції управління процесами та

потоками.

Короткі теоретичні відомості.

У многозадачных операційних системах (MS Windows, Linux й ін.) програму, що виконується під керуванням операційної системи (ОС), прийнято називати додатком операційної системи (application), або, що те ж, процесом (process). Звичайно в ОС паралельно (або псевдопаралельно, у режимі поділу процесорного часу) виконується велика кількість процесів. Для виконання процесу на апаратному рівні підтримується незалежне від інших процесів віртуальний адресний простір. Спроби процесу вийти за межі адрес цього простору відслідковуються апаратно.

Така модель зручна для розмежування незалежних програм. Однак у багатьох випадках вона не підходить, і доводиться використати подпроцессы (subprocesses), або, більше вживану назву, threads . Дослівний переклад слова threads - “нитки”. Іноді їх називають легковагими процесами (lightweight processes), тому що за інших рівних умов вони споживають набагато менше ресурсів, чим процеси. Ми будемо вживати термін “потоки виконання”, оскільки термін multithreading – роботу в умовах існування декількох потоків,- на російську мову набагато краще переводиться як многопоточность. Слід дотримуватися акуратності, щоб не плутати threads з потоками вводу-виводу (streams).

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

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

Будь-яка програма Java неявно використає потоки виконання. У головному потоці віртуальна Java-машина (JVM) запускає метод main додатка, а також всі методи, викликувані з нього. Головному потоку автоматично дається ім'я ”main”. Крім головного потоку у фоновому режимі (з малим пріоритетом) запускається дочірній потік, що займається зборкою сміття. Віртуальна Java-машина автоматично стартує при запуску на комп'ютері хоча б одного додатка Java, і завершує роботу у випадку, коли в неї на виконанні залишаються тільки потоки-демони.

В Java кожен потік виконання розглядається як об'єкт. Але цікаво те, що в Java кожен об'єкт, що навіть не має ніякого відношення до класу Thread, може працювати в умовах многопоточности, оскільки в класі Object визначені методи об'єктів, призначені для взаємодії об'єктів у таких умовах. Це notify(), notifyAll(), wait(), wait(timeout) -“сповістити”, “сповістити всіх”, “чекати”, “чекати до витікання таймаута”. Про ці методи буде розказано далі.

Є два способи створити клас, екземплярами якого будуть потоки виконання: успадкувати клас від java.lang.Thread або реалізувати інтерфейс java.lang.Runnable. Цей інтерфейс має декларацію єдиного методу public void run(), що забезпечує послідовність дій при роботі потоку. При цьому клас Thread уже реалізує інтерфейс Runnable, але з порожньою реалізацією методу run().Так що при створенні екземпляра Thread створюється потік, що нічого не робить. Тому в нащадку треба перевизначити метод run(). У ньому варто написати реалізацію алгоритмів, які повинні виконуватися в даному потоці. Відзначимо, що після виконання методу run() потік припиняє існування – “умирає”.

Розглянемо перший варіант, коли ми успадковуємо клас від класу Thread , перевизначивши метод run().

Об'єкт-потік створюється за допомогою конструктора. Є кілька перевантажених варіантів конструкторів, найпростіший з них - з порожнім списком параметрів. Наприклад, у класі Thread їхні заголовки виглядають так:

public Thread() – конструктор за замовчуванням. Подпроцесс одержує ім'я “system”.

public Thread(String name) - потік одержує ім'я, що втримується в рядку name.

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

У класі-нащадку можна викликати конструктор за замовчуванням (без параметрів), або задати свої конструктори, використовуючи виклики прабатьківських за допомогою виклику super(список параметрів). Через відсутність спадкування конструкторів в Java доводиться в спадкоємці заново задавати конструктори з тією же сигнатурою, що й у класі Thread. Це є простий, але стомлюючою роботою. Саме тому звичайно віддають перевагу способу завдання класу з реалізацією інтерфейсу Runnable, про що буде розказано декількома рядками пізніше.

Створення й запуск потоку здійснюється в такий спосіб:

public class T1 extends Thread{

public void run(){

...

}

...

}

Thread thread1= new T1();

thread1.start();

Другий варіант - використання класу, у якому реалізований інтерфейс java.lang.Runnable. Цей інтерфейс, як уже говорилося, має єдиний метод public void run(). Реалізувавши його в класі, можна створити потік за допомогою перевантаженого варіанта конструктора Thread:

public class R1 implements Runnable{

public void run(){

...

}

...

}

Thread thread1= Thread( new R1() );

thread1.start();

Звичайно таким способом користуються набагато частіше, тому що в розроблювальному класі не доводиться займатися дублюванням конструкторів класу Thread. Крім того, цей спосіб можна застосовувати у випадку, коли вже є клас, що належить ієрархії, у якій базовим класом не є Thread або його спадкоємець, і ми хочемо використати цей клас для роботи усередині потоку. У результаті від цього класу ми одержуємо метод run(), у якому реалізований потрібний алгоритм, і цей метод працює усередині потоку типу Thread, що забезпечує необхідне поводження в многопоточной середовищу. Однак у цьому випадку утрудняється доступ до методів із класу Thread - потрібне приведення типу.

Наприклад, щоб вивести інформацію про пріоритет потоку, у першому способі створення потоку в методі run() треба написати оператор

System.out.println("Пріоритет потоку="+this.getPriority());

А в другому способі доводитися це робити в кілька етапів. По-перше, при завданні класу нам варто додати в об'єкти типу R1 поле thread:

public class R1 implements Runnable{

public Thread thread;

public void run() {

System.out.println("Пріоритет потоку="+thread.getPriority());

}

}

За допомогою цього поля ми будемо добиратися до об'єкта-потоку. Але тепер після створення потоку необхідно не забути встановити для цього поля посилання на створений об'єкт-потік. Так що створення й запуск потоку буде виглядати так:

R1 r1=new R1();

Thread thread1=new Thread(r1, "thread1");

r1.thread=thread1;

thread1.start();//або, що те ж, r1.thread.start()

Через поле thread ми можемо одержувати доступ до потоку й всіх його полів і методам в алгоритмі, написаному в методі run(). Зазначені вище додаткові дії – це всього три зайвих рядки програми (перша - R1 r1=new R1(); друга - r1.thread=thread1; третя - оголошення в класі R1 - public Thread thread;) .

Як уже говорилося раніше, прямо давати доступ до поля даних – дурний тон програмування. Виправити цей недолік нашої програми просто: у дереві елементів програми вікна Projects у розділі Fields (“поля”) клацнемо правою кнопкою миші по імені thread і виберемо в що появились спливаючому меню Refactor/Encapsulate Fields… (“Провести рефакторинг”/ “Инкапсулировать поля...”). У діалозі, що з'явився, натиснемо на кнопку “Next>” і проведемо рефакторинг, підтвердивши вибір у нижнім вікні.

У класі Thread є кілька перевантажених варіантів конструктора з параметром типу Runnable:

public Thread(Runnable target) – з ім'ям “system” за замовчуванням.

public Thread(Runnable target, String name) – із завданням імені.

Також є варіанти із завданням групи потоків.

У класі Thread є ряд полів даних і методів, про які треба знати для роботи з потоками.

Найважливіші константи й методи класу Thread:

  • MIN_PRIORITY – мінімально можливий пріоритет потоків. Залежить від операційної системи й версії JVM. На комп'ютері автора виявився дорівнює 1.

  • NORM_PRIORITY - нормальний пріоритет потоків. Головний потік створюється з нормальним пріоритетом, а потім пріоритет може бути змінений. На комп'ютері автора виявився дорівнює 5.

  • MAX_PRIORITY – максимально можливий пріоритет потоків. На комп'ютері автора виявився дорівнює 10.

  • static int activeCount() - повертає число активних потоків додатка.

  • static Thread currentThread() – повертає посилання на поточний потік.

  • boolean holdsLock(Object obj) – повертає true у випадку, коли який-небудь потік (тобто поточний потік) блокує об'єкт obj.

  • static boolean interrupted() – повертає стан статусу переривання поточного потоку, після чого встановлює його в значення false.

Найважливіші методи об'єктів типу Thread:

  • void run() – метод, що забезпечує послідовність дій під час життя потоку. У класі Thread задана його порожня реалізація, тому в класі потоку він повинен бути перевизначений. Після виконання методу run()потік умирає.

  • void start() – викликає виконання поточного потоку, у тому числі запуск його методу run() у потрібному контексті. Може бути викликаний усього один раз.

  • void setDaemon(boolean on) – у випадку on==true установлює потоку статус демона, інакше – статус користувальницького потоку.

  • boolean isDaemon() - повертає true у випадку, коли поточний потік є демоном.

  • void yield() – “поступитися правами” – викликає тимчасове припинення потоку, з передачею права іншим потокам виконати необхідні їм дії.

  • long getId() – повертає унікальний ідентифікатор потоку. Унікальність ставиться тільки вчасно життя потоку - після його завершення (смерті) даний ідентифікатор може бути привласнений іншому створюваному потоку.

  • String getName() – повертає ім'я потоку, що йому було задано при створенні або методом setName.

  • void setName(String name) – установлює нове ім'я потоку.

  • int getPriority() - повертає пріоритет потоку.

  • void setPriority(int newPriority) – установлює пріоритет потоку.

  • void checkAccess() – здійснення перевірки з поточного потоку на позволительность доступу до іншого потоку. Якщо потік, з якого йде виклик, має право на доступ, метод не робить нічого. Інакше - збуджує виключення SecurityException.

  • String toString() – повертає строкове подання об'єкта потоку, у тому числі – його ім'я, групу, пріоритет.

  • void sleep(long millis) – викликає припинення (“засипання”) потоку на millis миллисекунд. При цьому всі блокування (монітори) потоку зберігаються. Перевантажений варіант sleep(long millis,int nanos)- параметр nanos задає число наносекунд. Дострокове пробудження здійснюється методом interrupt() – з порушенням виключення InterruptedException.

  • void interrupt() -перериває “сон” потоку, викликаний викликами wait(…)або sleep(...), установлюючи йому статус перерваного (статус переривання=true). При цьому збуджується виняткова ситуація, що перевіряє, InterruptedException.

  • boolean isInterrupted() - повертає поточний стан статусу переривання потоку без зміни значення статусу.

  • void join() – “злиття”. Переводить потік у режим умирання - очікування завершення (смерті). Це очікування – виконання методу join() - може тривати як завгодно довго, якщо відповідний потік на момент виклику методу join() блокований. Тобто якщо в ньому виконується синхронизованный метод або він очікує завершення синхронизованного методу. Перевантажений варіант join(long millis) - очікувати завершення потоку протягом millis миллисекунд. Виклик join(0) еквівалентний виклику join(). Ще один перевантажений варіант join(long millis,int nanos)- параметр nanos задає число наносекунд. Очікування смерті може бути перервано іншим потоком за допомогою методу interrupt() – з порушенням виключення InterruptedException. Метод join() є аналогом функції join в UNIX. Звичайно використається для завершення головним потоком роботи всіх дочірніх користувальницьких потоків (“злиття” їх з головним потоком).

  • boolean isAlive() - повертає true у випадку, коли поточний потік живий (не вмер). Відзначимо, що навіть якщо потік завершився, від нього залишається об'єкт-“примара”, що відповідає на запит isAlive()значенням false – тобто що повідомляє, що об'єкт умер.