Ім'я файлу: Lab12_Java_Vkazivky_2010_Multithreading.doc
Розширення: doc
Розмір: 106кб.
Дата: 17.05.2022
скачати

МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ

Національний технічний університет

«Харківський політехнічний інститут»
Кафедра «Фізичне виховання»

Реферат

з дисципліни «Основи розподілених баз даних»

Тема: «Багатопотоковість у Java»


Виконав студент групи КН.218.В

Музика Олександр
Перевірив викладач

Шевченко Сергій

Харків 2022

ЗМІСТ
1 ВВЕДЕННЯ 3

2 ОСНОВНА ЧАСТИНА 5

2.1 Оператори мови JAVA 5

2.2 Параллелизм лише на рівнібитов 6

2.3 Паралелізм лише на рівні інструкцій 6

2.4 Паралелізм даних 7

2.5 Паралелізм завдань (багатопотоковість) 7

2.6 Розподілені операційні системи 7

ВИСНОВКИ 10

ДЖЕРЕЛА 11

1. МЕТА РОБОТИ

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

2.ОСНОВНІ ТЕОРЕТИЧНІ ВІДОМОСТІ

2.1. ОПЕРАТОРИ МОВИ JAVA

HYPERLINK \l "25"

17.1. Процес
Основне поняття сучасних операційних систем — процесс(process). Як і всі загальні поняття, процес важко визначити. Можна розуміти під процесом виконувану (runnable) програму, але треба памятати про те, що у процесу єсть декілька станів. Процес може в будь-який момент перейти до виконання машинного коду іншої програми, а також "заснути" (sleep) на деякий час, призупинивши виконання програми. Він може бути вивантажений на диск. Кількість станів процесу і їх особливості залежать від операційної системи.
Всі сучасні операційні системи багатозадачні(multitasking), вони запускають і виконують зразу декілька процесів. Одночасно може працювати браузер, текстовий редактор, музичний програвач. На екрані дисплея відкриваються декілька вікон, кожне з яких звязано із своїм працюючим процесом. Якщо на компютері тільки один процессор, то він переключається з одного процесу на другий, створюючи видимість одночасної роботи. Переключення відбувається по закінченню одного або декількох "тиків" (ticks). Розмір тику залежить від тактової частоти процесора і звичайно має порядок 0,01 секунди. Процесам назначаються різні пріоритети(priority). Процеси з низьким пріоритетом не можуть перервати виконання процесу з більш високим пріоритетом, вони менше займають процесор, тому виконуються повільно, як говорять, "на фоні". Самий високий пріоритет у системних процесів, наприклад, у диспетчера(scheduler), який як раз і займається переключенням процесора з процесу на процесс. Такі процеси не можна переривати, поки вони не закінчать работу, інакше компютер швидко прийде в хаотичний стан.
Кожному процесу виділяється певна область оперативної памяті для розміщення коду програми і її даних — його адресний простір. В цю ж область записується частина даних про процес, складаюча його контекст(context). Дуже важливо розділить адресний простір різних процесів, щоб вони не могли змінити код і дані один одного. Операційні системи по-різному відносяться до забезпечення захисту адресних просторів процесів. MS Windows NT/2000 ретельно розділяють адресні простори, витрачаючи на це багато ресурсів і часу. Це підвищує надійність виконання програми, але утруднює створення процесу. Такі операційні системи погано справляються з управлінням великого числа процесів.
Операційні системи сімейства UNIX менше турбуються про захист памяті, але легше створюють процеси і здатні управляти сотнею одночасно працюючих процесів. Крім управління работою процесів операційна система повинна забезпечити засоби їх взаємодії: обмін сигналами і повідомленнями, створення спільних декільком процесам областей памяті і виконуваного коду програми. Ці засоби також вимагають ресурсів і уповільнюють роботу компютера.
Роботу багатозадачної системи можна спроститс і прискорити, якщо дозволити взаємодіючим процесам працювати в одному адресному просторі. Такі процесси називаються threads. Буквальний переклад - "нитка", але ми зупинимося на слові "підпроцес". Підпроцеси створюють нові труднощі для операційної системи — треба дуже уважно слідкувати за тим, щоб вони не заважали один одному при запису в спільні ділянки памяті, — але зате полегшують взаємодію підпроцесів. Створення підпроцесів і управління ними — це справа операційної системи, але в Java введені засоби для виконання цих дій. Оскільки програми, написані на Java, повинні працювати у всіх операційних системах, ці засоби дозволяють виконувати тільки самі загальні дії.
Коли операційна система запускає віртуальну машину Java для виконання додатку, вона створює один процес з декількома підпроцесами. Головний(main) підпроцес виконує байт-коди програми, а саме, він зразу ж звертається до методу main() додатку. Цей підпроцес може породити нові підпроцеси, які, в свою чергу, здатні породить підпроцеси і т. д. Головним підпроцесом аплета являеться один із підпроцесів браузера, в якому аплет виконується. Головний підпроцес не грає ніякої особливої ролі, просто він створюється першим.
Підпроцес в Java створюється і управляється методами класу Thread. Після створення обєкта цього класу одним із його конструкторів новий підпроцес запускається методом start (). Отримати посилку на поточний підпроцес можна статичним методом Thread.currentThread() ;
Клас Thread реалізує інтерфейс RunnabІe. Цей інтерфейс описує тільки один метод run(). Новий підпроцес буде виконувати те, що записано в цьому методі. Між іншим, клас Thread містить тільки пусту реалізацію методу run(), тому клас Thread не використовується сам по собі, він завжди розширюється. При його розширенні метод run() перевизначається. Метод run() не містить аргументів, так як нікому передавати їх значення в метод. Він не повертає значення, його нікуди передавати. До методу run() не можна звернутися із програми, це завжди робиться автоматично виконуючою системою Java при запуску нового підпроцесу методом start ().
Отже, задати дії створюваного підпроцесу можна двома способами: розширити клас Thread або реалізувати інтерфейс RunnabІe. Перший спосіб дозволяє використовувати методи класу Thread для управління підпроцесом. Другий спосіб застосовується в тих випадках, коли треба тільки реалізувати метод run(), або клас, створюючий підпроцес, уже розширяє якийсь інший клас. Подивимося, які конструктори і методи містить клас Thread.
17.2. Клас Thread
В класі Thread сім конструкторів:
Thread(ThreadGroup group, Runnable target, String name) — створює підпроцес з іменем name, належний групі group і виконуючий метод run() обєкта target. Це основной конструктор, всі інші звертаються до нього з тим чи іншим параметром, рівним null;

Thread() — створюваний підпроцесс буде виконувати свій метод run();

Thread(Runnable target);

Thread(Runnable target, String name);

Thread(String name);

Thread(ThreadGroup group, Runnable target);

Thread(ThreadGroup group, String name).
Імя підпроцесу name не має ніякого значення, воно не використовується віртуальною машиною Java і застосовується тільки для відрізняння підпроцесів в програмі. Після створення підпроцесу його треба запустити методом start(). Віртуальна машина Java почне виконувати метод run() цього обєкта-підпроцеса. Підпроцес завершить роботу після виконання метода run(). Для знищення обєкта-підпроцеса вслід за цим він повинен присвоїти значення null.
Виконуваний підпроцес можна призупинити статичним методом sleep (long ms) на ms мілісекунд. Цей метод ми уже використовували в попередніх уроках. Якщо обчислювальна система може відраховувати наносекунди, то можна призупинити підпроцес з точністю до наносекунд методом sleep(long ms, int nanosec). В лістинзі 17.1 приведено найпростіший приклад. Головний підпроцесс створює два підпроцеси з іменами Thread1 і Thread 2, виконуючих один і той же метод run(). Цей метод просто виводить 20 раз текст на екран, а потім повідомляє про своє завершення.
Лістинг 17.1. Два підпроцеси, запущені із головного підпроцесу

class OutThread extends Thread{

private String msg;

OutThread(String s, String name){

super(name); msg = s;

}

public void run()

{

for(int i = 0; i < 20; i++){

// try{

// Thread.sleep(100);

// }catch(InterruptedException ie){}

System.out.print(msg + " ");

}

System.out.println("End of " + getName());

}

} class TwoThreads{

public static void main(String[] args){

new OutThread("HIP", "Thread 1").start();

new OutThread("hop", "Thread 2").start();

System.out.println();

}

}
На рис. 17.1 показано результат двох запусків програми лістингу 17.1. Як бачите, в першому випадку підпроцес Thread1 встиг відпрацювати повністю до переключення процесора на виконання другого підпроцесу. В другому випадку робота підпроцеса Thread1була перервана, процесор переключився на виконання підпроцеса Thread 2, встиг виконати його повністю, а потім переключився знову на виконання підпроцеса Thread1 і завершив його.

Рис. 17.1. Два підпроцеси працюють без затримки
Це дуже повчальний приклад, але якщо у вас сучасний компютер з більшою швидкістю дії, то запустивши на ньому програму лістингу 17.1 ви можете побачити зовсім іншу картину. Підпроцеси можуть спрацювати так швидко, що переключення не здійсниться.
Приберемо в лістинзі 17.1 коментарі, затримавши тим самим виконання кожної ітерації циклу на 0,1 секунди. Пуста обробка виключення InterruptedException означає, що ми ігноруємо спробу переривання роботи підпроцеса. На рис. 17.2 показано результат двох запусків програми. Як бачите, процесор переключається з одного підпроцеса на інший, але в одному місці регулярність переключення порушується і раніше запущений підпроцес завершується пізніше.
Як же досягти узгодженості, як говорять, синхронізації (synchronization) підпроцесів? Обговоримо це нижче, а поки що покажемо ще два варіанти створення тієї ж самої програми. В лістинзі 17.2 приведено другий варіант тієї ж програми: сам клас TwoThreads2 являється розширенням класу Thread, а метод run() реалізується прямо в ньому.
Лістинг 17.2. Клас розширює Thread
class TwoThreads2 extends Thread{

private String msg;

TwoThreads2(String s, String name){

super(name); msg = s;

}

public void run(){

for(int i = 0; i < 20; i++){

try{

Thread.sleep(100);

}catch(InterruptedException ie){}

System.out.print(msg + " ");

}

System.out.println("End of " + getName());

}

public static void main(String[] args)(

new TwoThreads2("HIP", "Thread 1").start();

new TwoThreads2("hop", "Thread 2").start();

System.out.println();

}

}
Третій варіант: клас TwoThreads3 реалізує інтерфейс RunnabІe. Цей варіант записаний в лістинзі 17.3. Тут не можна використовувати методи класу Thread, але зате клас TwoThreads3 може бутиь розширенням іншого класу. Наприклад, можна зробити його аплетом, розширивши клас Applet або JAppІet.
Лістинг 17.3. Реалізація інтерфейса Runnabie
class TwoThreadsS implements RunnabІe{

private String msg;

TwoThreads3(String s){ msg = s; }

public void run(){

forfint i = 0; i < 20; i++){

try{

Thread.sleep(100);

}catch(InterruptedException ie){}

System.out.print(msg + " ");

}

System.out.println("End of thread.");

}

public static void main (String[] args){

new Thread(new TwoThreads3("HIP"), "Thread 1").start ();

new Thread(new TwoThreads3("hop"), "Thread 2").start ();

System.out.println();

}

}


Рис. 17.2. Підпроцеси працюють із затримкою
Частіше всього в новому підпроцесі задаються нескінчені дії, виконувані на фоні основних дій: програється музика, на екрані крутиться анімований логотип фірми, біжить рекламний рядок. Для реалізації такого підпроцеса в методі run() задається нескінчений цикл, зупинюваний після того, як обєкт-підпроцес отримає значення null. В лістинзі 17.4 показано четвертий варіант тієї ж самої програми, в якій метод run() виконується до тих пір, доки поточний обєкт-підпроцес th співпадає з обєктом gо, запустившим поточний підпроцес. Для переривання його виконання передбачений метод stop(), до якого звертається головний підпроцес. Ця стандартна конструкція, рекомендована документацією J2SDK. Головний підпроцес в даному прикладі тільки створює обєкти-підпроцеси, чекає одну секунду і зупиняє їх.
Лістинг 17.4. Зупинка роботи підпроцесів
class TwoThreadsS implements Runnabie{

private String msg;

private Thread go;

TwoThreadsS(String s){

msg = s;

go = new Thread(this);

go.start();

}

public void run(){

Thread th = Thread.currentThread();

while(go == th){

try{

Thread.sleep(100);

}catch(InterruptedException ie){}

System.out.print(msg + " ");

}

System.out.println("End of thread.");

}

public void stop(){ go = null; }

public static void main(String[] args){

TwoThreadsS thl = new TwoThreadsS("HIP");

TwoThreadsS th2 = new TwoThreadsS("hop");

try{

Thread.sleep(1000);

}catch(InterruptedException ie){}

thl.stop(); th2.stop();

System.out.printlnf);

}

}
17.3. Синхронізація підпроцесів
Основна складність при написанні програм, в яких працюють декілька підпроцесів — це узгодити сумісну роботу підпроцесів із загальними комірками памяті. Класичний приклад — банківська трансакція, в якій змінюється залишок на рахунку клієнта з номером numDep. Припустимо, що для її виконання запрограмовані такі дії:
Deposit myDep = getDeposit(numDep); // Отримуємо рахунок з номером numDep

int rest = myDep.getRest();// Отримуємо залишок на рахунку myDep

Deposit newDep = myDep.operate(rest, sum); // Змінюємо залишок на величину sum

myDep.setDeposit(newDep); // Заносимо новий залишок на рахунок myDep
Нехай на рахунку лежить 1000 гривнів. Ми вирішили зняти з рахунку 500 гривнів, а в той же час поступив поштовий переказ на 1500 гривнів. Ці дії виконують різні підпроцеси, але змінюють вони один і той же рахунок myDep з номером numDep. Подивившись ще раз на рис. 17.1 і 17.2, ви повірите, що послідовність дій може скластися так. Перший підпроцес проробить віднімання 1000 -500, в цей час другий підпроцес виконає всі три дії і запише на рахунок 1000+1500 = 2500 гривнів, після чого перший підпроцес виконає свою останню дію і у нас на рахунку виявиться 500 гривнів. Навряд ии вам сподобається таке виконання двох трансакцій.
В мові Java прийнятий виход із цього положення, названий в теорії операційних систем монітором (monitor). Він заключається в тому, що підпроцес блокує обєкт, з яким працює, щоб другі підпроцеси не могли звернутися до даного обєкту, поки блокування не буде знято. В нашому прикладі перший підпроцес повинен спочатку заблокувати рахунок myDep, потім повністю виконати всю трансакцію і зняти блокування. Другий підпроцес призупиниться і стане чекати, поки блокування не буде знято, після чого почне працювати з обєктом myDep. Все це робиться одним оператором synchronized() {}, як показано нижче:
Deposit myDep = getDeposit(numDep);

synchronized(myDep){

int rest = myDep.getRest();

Deposit newDep = myDep.operate(rest, sum);

myDep.setDeposit(newDep);

}
В заголовку оператора synchronized в дужках указується посилання на обєкт, який буде заблокований перед виконанням блоку. Обєкт буде недоступний для інших підпроцесів, поки виконується блок. Після виконання блоку блокування знімається. Якщо при написанні якогось методу виявилось, що в блок synchronized входять всі оператори цього методу, то можна просто помітить метод словом synchronized, зробивши його синхронізованим (synchronized):
synchronized int getRest()(

// Тіло методу

}

synchronized Deposit operate(int rest, int sum) {

// Тіло методу

}

synchronized void setDeposit(Deposit dep){

// Тіло методу

}
В цьому випадку блокується объект, виконуючий метод, тобто this. Якщо всі методи, до яких не повинні одночасно звертатися декілька підпроцесів, помічені synchronized, то оператор synchronized() {} уже не потрібний. Тепер, якщо один підпроцес виконує синхронізований метод обєкта, то інші підпроцеси уже не можуть звернутися до жодного синхронізованого методу того ж самого обєкта.
Приведемо простий приклад. Метод run() в лістинзі 17.5 виводить рядок "Hello, World!" із затримкою в 1 секунду між словами. Цей метод виконується двома підпроцесами, працюючими з одним обєктом th. Програма виконується два рази. Перший раз метод run() не синхронзований, другий раз синхронізований, його заголовок показано в лістинзі 17.4 як коментар. Результат виконання програми представлений на рис. 17.3.
Лiстинг 17.5. Синхронізація методу
class TwoThreads4 implements Runnable{

public void run(){

// synchronized public void run(){

System.out.print("Hello, ");

try{

Thread.sleep(1000);

}catch(InterruptedException ie){}

System.out.println("World!");

}

public static void main(String[] args){

TwoThreads4 th = new TwoThreads4();

new Thread(th).start();

new Thread(th).start();

}

}


Рис. 17.3. Синхронізація методу
Дії, що входять в синхронізований блок або метод створюють критичну ділянку (critical section) програми. Декілька підпроцесів, що збираються виконувати критичну ділянку, стають в чергу. Це сповільнює роботу програми, тому для швидкого її виконання критичних ділянок повинно бути як можна менше, і вони повинні бути як можна коротші. Багато методів Java 2 SDK синхронізовані. Зверніть увагу, що на рис. 17.1 слова виводяться впереміжку, але кожне слово виводиться повністю. Це відюувається тому, що метод print() класу Printstream синхронізований, при його виконанні вихідний потік system.out блокується до тих пір, доки метод print () не закінчить свою роботу.
Отже, ми можемо легко організувати послідовний доступ декількох підпроцесів до полів одного обєкта за допомогою оператора synchronized() {}. Синхронізація забезпечує взаємно виключаюче (mutually exclusive) виконання підпроцесів. Але що робити, якщо потрібний сумісний доступ декількох підпроцесів до спільних обєктів? Для цього в Java існує механізм очікування і сповіщення (wait-notify).
17.4. Узгодження роботи декількох підпроцесів
Можливість створення багатопотокових програм закладена в Java з самого її створення. В кожному обєкті єсть три методи wait() і один метод notify(), дозволяючі призупиняти роботу підпроцесу з цим обєктом, дозволити іншому підпроцесу попрацювати з обєктом, а потім сповістити (notify) перший підпроцес про можливість продовження роботи. Ці методи визначені прямо в класі object і наслідуються всіма класами. З кожним обєктом звязано багато підпроцесів, очікуючих доступу до обєкту (wait set). Спочатку цей "зал очікування" порожній.
Основний метод wait (long miІІisec) призупиняє поточний підпроцес this, працюючий з обєктом, на miІІisec мілісекунд і переводить його в "зал очікування", в множину очікуючих підпроцесів. Звернення до цього методу допускається тільки в синхронізованому блоці або методі, щоб бути впевненими в тому, що з обєктом працює тільки один підпроцес. Через miІІisec або після того, як обєкт отримає сповіщення методом notify(), підпроцес готовий відновити роботу. Якщо аргумент miІІisec рівний 0, то час очікування не визначено і відновлення роботи підпроцесу можна тільки після того, як обєкт отримає сповіщення методом notify(). Відміна даного методу від методу sleep() в тому, що метод wait() знімають блокування з обєкта. З обєктом може працювати один із підпроцесів із "зала очікування", звичайно той, який чекав довше всіх, хоч це не гарантуеться специфікацією JLS.
Другий метод wait () еквівалентний wait(0). Третій метод wait (long millisec, int nanosec) уточнює затримку на nanosec наносекунд, якщо їх зуміє відрахувати операційна система. Метод notify() виводить із "зали очікування" тільки один, довільно вибраний підпроцес. Метод notifyAll() виводить із стану очікування всі підпроцеси. Ці методи теж повинні виконуватися в синхронізованому блоці або методі. Як же застосувати все це для узгодженого доступу до обєкта? Як завжди, краще всього пояснити це на прикладі.

Звернемося знову до схеми "постачальик-споживач", уже використану в уроці15. Один підпроцес, постачальник, робить обчислення, другий, споживач, очікує результати цих обчислень і використовує їх в міру поступання. Підпроцеси передають інформацію через спільний екземпляр st класу store. Робота цих підпроцесів повинна бути узгоджена. Споживач зобовязаний чекати, доки постачальник не занесе результат обчислення в обєкт st, а постачальник повинен чекати, доки споживач не візьме цей результат. Для простоти постачальник просто заносить в спільний обєкт класу store цілі числа, а споживач лише забирає їх. В лістинзі 17.6 клас store не забезпечує узгодженості отримання і видачі інформацію. Результат роботи показаний на рис. 17.4.
Лістинг 17.6. Неузгоджені підпроцеси

class Store{

private inf inform;

synchronized public int getlnform(){ return inform; }

synchronized public void setlnform(int n){ inform = n; }

}

class Producer implements Runnable{

private Store st;

private Thread go;

Producer(Store st){

this.st = st;

go = new Thread(this);

go.start();

}

public void run(){

int n = 0;

Thread th = Thread.currentThread();

while(go == th){

st.setlnform(n);

System.out.print("Put: " + n + " ");

n++;

}

}

public void stop(){ go = null;

}

}

class Consumer implements Runnable{

private Store st;

private Thread go;

Consumer(Store st){

this.st = st;

go =-new Thread(this);

go.start () ;

}

public void run(){

Thread th = Thread.currentThread();

while(go == th) System.out.println("Got: " + st.getlnformf));

}

public void stop(){ go = null; }

}

class ProdCons{

public static void main(String[] args){

Store st = new Store();

Producer p = new Producer(st);

Consumer с = new Consumer(st);

try{

Thread.sleep(30);

}catch(InterruptedException ie){}

p.stop(); c.stop();

}

}


Рис. 17.4. Неузгоджена робота двох підпроцесів
В лістинзі 17.7 в клас store внесено логічне поле ready, що відмічає процес отримання і видачі інформації. Коли нова порція информації отримана від постачальника Producer, в полі ready заноситься значення true, отримувач consumer може забирати цю порцію інформації. Після видачі інформації змінна ready становиться рівною false. Але цього мало. Те, що отримувач може забрати продукт, не означає, що він дійсно забере його. Тому в кінці методу setinform() отримувач сповіщається про поступанні продукту методом notify(). Поки поле ready не прийме потрібне значення, підпроцес переводится в "залу очікування" методом wait(). Результат роботи програми з обновленим класом store показаний на рис. 17.5.
Лістинг 17.7. Узгодження отримання і видачі інформації
class Store{

private int inform = -1;

private boolean ready;

synchronized public int getlnform(){

try{

if (! ready) wait();

ready = false;

return inform;

}catch(InterruptedException ie){

}finally!

notify();

}

return -1;

}

synchronized public void setlnform(int n)(

if (ready)

try{

wait ();

}catch(InterruptedException ie){}

inform = n;

ready = true;

notify();

}

}
Оскільки сповіщення поставщика в методі getinform() повинно відбуватися уже після відправки інформації оператором return inform, воно включено в блок finally{}

Зверніть увагу: повідомлення "Got: 0" відстає на один крок від дійсного отримання інформації.

Рис. 17.5. Узгоджена робота підпроцесів
17.5. Пріоритети підпроцесів
Планувальник підпроцесів віртуальної машини Java призначає кожному підпроцесу однаковий час виконання процесором, переключаючись з підпроцеса на підпроцес по закінченню цього часу. Інколи необхідно виділити якомусь підпроцесу більше або менше часу в порівнянні з іншим підпроцесом. В такому випадку можна задати підпроцесу більший або менший пріоритет. В класі Thread єсть три цілі статичні константи, що задають пріоритети:
NORM_PRIORITY — звичайний пріоритет, який одержує кожний підпроцес при запуску, його числове значення 5;

MIN_PRIORITY — найменший пріоритет, його значення 1;

MAX_PRIORITY — найвищий пріоритет, його значення 10.

Крім цих значень можна задать будь-яке проміжне значення від 1 до 10, але треба памятати про те, що процесор буде переключатися між підпроцесами з однаковим вищим пріоритетом, а підпроцеси з меншим пріоритетом не стануть виконуватися, якщо тільки не призупинені всі підпроцеси з вищим пріоритетом. Тому для підвищення загальної продуктивності належить призупиняти час від часу методом sleep() підпроцеси з високим пріоритетом.
Установити той чи інший пріоритет можна в будь-який час методом setPriorityfint newPriority), якщо підпроцес має право змінювати свій пріоритет. Перевірити наявність такого права можна методом checkAtcess(). Цей метод викидає виключення класу SecurityЕxception, якщо підпроцес не може змінити свій пріоритет. Породжені підпроцеси будуть мати той же пріоритет, що і підпроцес-батько. Отже, підпроцеси, як правило, повинні працювати з пріоритетом NORM_PRIORITY. Підпроцеси більшу частину часу очікуючі настання якоїсь події, наприклад, натискання користувачем кнопки Вихід, можуть отримати більш високий пріоритет MAX_PRIORITY. Підпроцеси, виконуючі тривалу роботу, наприклад, установку мережевого зєднання або рисування зображення в памяті при подвійній буферизації, могжть працювати з нижчим пріоритетом MIN_PRIORITY.
17.6. Підпроцеси-демони
Робота програми починається з виконання метоуа main() головним підпроцесом. Цей підпроцес може породити інші підпроцеси, вони, в свою чергу, здатні породити свої підпроцеси. Після цього головний підпроцес нічим не буде відрізнятися від решти підпроцесів. Він не слідкує за породженими ним підпроцесами, не чекає від них ніяких сигналів. Головний підпроцес може завершитися, а програма буде продовжувати роботу, доки не закінчить роботу останній підпроцес. Це правило не завжди зручне. Наприклад, якийсь із підпроцесів може призупинитися, очікуючи мережевого зєднання, яке ніяк не може наступити. Користувач, не дочекавшись зєднання, зупиняє роботу головного підпроцесу, але програма продовжує працювати.
Такі випадки можна врахувати, оголосивши деякі підпроцеси демонами (daemons). Це поняття не співпадає з поняттям демона в UNIX. Просто програма завершується по закінченні роботи останнього користувальського (user) підпроцесу, не чекаючи закінчення роботи демонів. Демони будуть примусово завершені виконуючою системою Java. Оголосити підпроцес демоном можна зразу після його створення, перед запуском. Це робиться методом setDaemon(true). Даний метод звертаэться до методу checkAccess() і може викинути SecurityException. Змінити статус демона після запуску процесу уже неможна. Всі підпроцеси, породжені демоном, теж будуть демонами. Для зміни їх статусу необхідно звернутися до методу setDaemon(false).
17.7. Групи підпроцесів
Підпроцеси обєднуються в групи. На початку роботи програми виконуюча система Java створює групу підпроцесів з іменем main. Всі підпроцеси по замовчуванню попадають в цю групу. В будь-який час програма може створити новіе групи підпроцесів і підпроцеси, що входять в ці групи. Спочатку створюється група — екземпляр класуа ThreadGroup, конструктором
ThreadGroup(String name)
При цьому група отримує імя, задане аргументом name. Потім цей екземпляр указується при створенні підпроцесіов в конструкторах класу Thread. Всі підпроцеси попадуть в групу з іменем, заданим при створенні групи. Групи підпроцесів можуть утворювати ієрархію. Одна група породжується від другої конструктором
ThreadGroup(ThreadGroup parent, String name)
Групи підпроцесіов використовуються головним чином для задання пріоритетів підпроцесам всередині групи. Зміна пріоритетів всередині групи не буде впливати на пріоритети підпроцесів зовні ієрархії цієї групиы. Кожна група маєт максимальний пріоритет, устанавлюваний методом setMaxPriority(int maxPri) класу ThreadGroup. Ні один підпроцес із цієї групи не може перевищити значення maxPri, але пріоритети підпроцесів, задані до установки maxPri, не змінюються.
17.8. Заключення
Технологія Java по своїй суті — багатозадачна технологія, основана на threads. Це одна із причин, по яких технологія Java так і не може розумним способом реалізовуватися в MS-DOS і Windows 3.1, незважаючи на багато спроб. Тому, конструюючи програму для Java, належить весь час памятати, що вона буде виконуватися в багатозадачному середовищі. Треба ясно представлять собі, що буде, якщо програма почне виконуватися одночасно кількома підпроцесами, виділяти критичні ділянки і синхронізовувати їх. З другого боку, якщои програма виконує декілька дій, треба подумати, чи не зробити їх виконання одночасним, створивши додаткові підпроцеси і розподіливши їх пріоритети.

В лістинзі 17.1 приведено найпростіший приклад. Головний підпроцесс створює два підпроцеси з іменами Thread1 і Thread 2, виконуючих один і той же метод run(). Цей метод просто виводить 20 раз текст на екран, а потім повідомляє про своє завершення.
Лістинг 17.1. Два підпроцеси, запущені із головного підпроцесу

class OutThread extends Thread{

private String msg;

OutThread(String s, String name){

super(name); msg = s;

}

public void run()

{

for(int i = 0; i < 20; i++){

// try{

// Thread.sleep(100);

// }catch(InterruptedException ie){}

System.out.print(msg + " ");

}

System.out.println("End of " + getName());

}

} class TwoThreads{

public static void main(String[] args){

new OutThread("HIP", "Thread 1").start();

new OutThread("hop", "Thread 2").start();

System.out.println();

}

}
В лістинзі 17.2 приведено другий варіант тієї ж програми: сам клас TwoThreads2 являється розширенням класу Thread, а метод run() реалізується прямо в ньому.
Лістинг 17.2. Клас розширює Thread
class TwoThreads2 extends Thread{

private String msg;

TwoThreads2(String s, String name){

super(name); msg = s;

}

public void run(){

for(int i = 0; i < 20; i++){

try{

Thread.sleep(100);

}catch(InterruptedException ie){}

System.out.print(msg + " ");

}

System.out.println("End of " + getName());

}

public static void main(String[] args)(

new TwoThreads2("HIP", "Thread 1").start();

new TwoThreads2("hop", "Thread 2").start();

System.out.println();

}

}
Третій варіант: клас TwoThreads3 реалізує інтерфейс RunnabІe. Цей варіант записаний в лістинзі 17.3. Тут не можна використовувати методи класу Thread, але зате клас TwoThreads3 може бутиь розширенням іншого класу. Наприклад, можна зробити його аплетом, розширивши клас Applet або JAppІet.
Лістинг 17.3. Реалізація інтерфейса Runnabie
class TwoThreadsS implements RunnabІe{

private String msg;

TwoThreads3(String s){ msg = s; }

public void run(){

forfint i = 0; i < 20; i++){

try{

Thread.sleep(100);

}catch(InterruptedException ie){}

System.out.print(msg + " ");

}

System.out.println("End of thread.");

}

public static void main (String[] args){

new Thread(new TwoThreads3("HIP"), "Thread 1").start ();

new Thread(new TwoThreads3("hop"), "Thread 2").start ();

System.out.println();

}

}
Частіше всього в новому підпроцесі задаються нескінчені дії, виконувані на фоні основних дій: програється музика, на екрані крутиться анімований логотип фірми, біжить рекламний рядок. Для реалізації такого підпроцеса в методі run() задається нескінчений цикл, зупинюваний після того, як обєкт-підпроцес отримає значення null. В лістинзі 17.4 показано четвертий варіант тієї ж самої програми, в якій метод run() виконується до тих пір, доки поточний обєкт-підпроцес th співпадає з обєктом gо, запустившим поточний підпроцес. Для переривання його виконання передбачений метод stop(), до якого звертається головний підпроцес. Ця стандартна конструкція, рекомендована документацією J2SDK. Головний підпроцес в даному прикладі тільки створює обєкти-підпроцеси, чекає одну секунду і зупиняє їх.
Лістинг 17.4. Зупинка роботи підпроцесів
class TwoThreadsS implements Runnabie{

private String msg;

private Thread go;

TwoThreadsS(String s){

msg = s;

go = new Thread(this);

go.start();

}

public void run(){

Thread th = Thread.currentThread();

while(go == th){

try{

Thread.sleep(100);

}catch(InterruptedException ie){}

System.out.print(msg + " ");

}

System.out.println("End of thread.");

}

public void stop(){ go = null; }

public static void main(String[] args){

TwoThreadsS thl = new TwoThreadsS("HIP");

TwoThreadsS th2 = new TwoThreadsS("hop");

try{

Thread.sleep(1000);

}catch(InterruptedException ie){}

thl.stop(); th2.stop();

System.out.printlnf);

}

}
Приведемо простий приклад. Метод run() в лістинзі 17.5 виводить рядок "Hello, World!" із затримкою в 1 секунду між словами. Цей метод виконується двома підпроцесами, працюючими з одним обєктом th. Програма виконується два рази. Перший раз метод run() не синхронзований, другий раз синхронізований, його заголовок показано в лістинзі 17.4 як коментар.
Лiстинг 17.5. Синхронізація методу
class TwoThreads4 implements Runnable{

public void run(){

// synchronized public void run(){

System.out.print("Hello, ");

try{

Thread.sleep(1000);

}catch(InterruptedException ie){}

System.out.println("World!");

}

public static void main(String[] args){

TwoThreads4 th = new TwoThreads4();

new Thread(th).start();

new Thread(th).start();

}

}
Звернемося знову до схеми "постачальик-споживач", уже використану в уроці15. Один підпроцес, постачальник, робить обчислення, другий, споживач, очікує результати цих обчислень і використовує їх в міру поступання. Підпроцеси передають інформацію через спільний екземпляр st класу store. Робота цих підпроцесів повинна бути узгоджена. Споживач зобовязаний чекати, доки постачальник не занесе результат обчислення в обєкт st, а постачальник повинен чекати, доки споживач не візьме цей результат. Для простоти постачальник просто заносить в спільний обєкт класу store цілі числа, а споживач лише забирає їх. В лістинзі 17.6 клас store не забезпечує узгодженості отримання і видачі інформацію. Результат роботи показаний на рис. 17.4.
Лістинг 17.6. Неузгоджені підпроцеси

class Store{

private inf inform;

synchronized public int getlnform(){ return inform; }

synchronized public void setlnform(int n){ inform = n; }

}

class Producer implements Runnable{

private Store st;

private Thread go;

Producer(Store st){

this.st = st;

go = new Thread(this);

go.start();

}

public void run(){

int n = 0;

Thread th = Thread.currentThread();

while(go == th){

st.setlnform(n);

System.out.print("Put: " + n + " ");

n++;

}

}

public void stop(){ go = null;

}

}

class Consumer implements Runnable{

private Store st;

private Thread go;

Consumer(Store st){

this.st = st;

go =-new Thread(this);

go.start () ;

}

public void run(){

Thread th = Thread.currentThread();

while(go == th) System.out.println("Got: " + st.getlnformf));

}

public void stop(){ go = null; }

}

class ProdCons{

public static void main(String[] args){

Store st = new Store();

Producer p = new Producer(st);

Consumer с = new Consumer(st);

try{

Thread.sleep(30);

}catch(InterruptedException ie){}

p.stop(); c.stop();

}

}
В лістинзі 17.7 в клас store внесено логічне поле ready, що відмічає процес отримання і видачі інформації. Коли нова порція информації отримана від постачальника Producer, в полі ready заноситься значення true, отримувач consumer може забирати цю порцію інформації. Після видачі інформації змінна ready становиться рівною false. Але цього мало. Те, що отримувач може забрати продукт, не означає, що він дійсно забере його. Тому в кінці методу setinform() отримувач сповіщається про поступанні продукту методом notify(). Поки поле ready не прийме потрібне значення, підпроцес переводится в "залу очікування" методом wait(). Результат роботи програми з обновленим класом store показаний на рис. 17.5.
Лістинг 17.7. Узгодження отримання і видачі інформації
class Store{

private int inform = -1;

private boolean ready;

synchronized public int getlnform(){

try{

if (! ready) wait();

ready = false;

return inform;

}catch(InterruptedException ie){

}finally!

notify();

}

return -1;

}

synchronized public void setlnform(int n)(

if (ready)

try{

wait ();

}catch(InterruptedException ie){}

inform = n;

ready = true;

notify();

}

}


3. ЗМІСТ ЗВІТУ

Мета роботи.

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

Файли проекту.

Аналіз результатів та висновки.

СПИСОК РЕКОМЕНДОВАНОЇ ЛІТЕРАТУРИ

Bruce Eckel, Thinking in Java, 2nd Edition, 2000.

Christopher Batty and David Scuse, Installing Eclipse. Department of Computer Science, University of Manitoba, Winnipeg, Manitoba, Canada, 2003.
скачати

© Усі права захищені
написати до нас