МІНІСТЕРСТВО ОСВІТИ ТА НАУКИ УКРАЇНИ
НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ “ЛЬВІВСЬКА ПОЛІТЕХНІКА”
Кафедра САПР
ЗВІТ
До виконання лабораторної роботи №3
На тему: “ Використання потоків в Java”
З курсу “ Об’єктно-орієнтоване програмування”
Львів – 2009
МЕТА РОБОТИ
Метою роботи є придбання навиків роботи з потоками при програмуванні на мові Java.
КОРОТКІ ТЕОРЕТИЧНІ ВІДОМОСТІ
2.1. РEАЛІЗАЦІЯ ПОТОКІВ В JAVA
Мова Java є однією з небагатьох мов програмування, які містять засоби підтримки потоків. У мові Java потоки звичайно використовуються для того, щоб аплети могли виконувати якісь дії в той час, як Web-браузер продовжує свою роботу, проте потоки можна застосувати в будь-якій програмі при необхідності паралельного виконання декількох завдань.
Так, наприклад, при створенні колективом програмістів великого і складного програмного продукту, як правило, окремі модулі програми розробляються паралельно окремими програмістами або групами програмістів. В цьому випадку процес розробки кожного модуля програми можна представити як окремий потік.
Реалізація використання потоків в програмах на мові може виконуватися двома способами:
- розширенням класу Thread;
- реалізацією інтерфейсу Runnable.
При першому способі клас стає потоковим, якщо він створений як розширення класу Thread, який визначений в пакеті java.lang, наприклад:
public class GreatRace extends Thread
При цьому стають доступними всі методи потоків. Звичайно, коли необхідно, щоб даний клас є розширенням деякого іншого класу і в ньому необхідно реалізувати потоки, попередній підхід не можна використовувати, оскільки, як вже указувалося, мові Java немає множинного спадкоємства. Для вирішення цієї проблеми для даного класу потрібно реалізувати інтерфейс Runnable, наприклад:
· public class GreatRace extends Applet implements Runnable
· Інтерфейс Runnable має тільки один метод public void run(). Насправді клас Thread також реалізує цей інтерфейс, проте стандартна реалізація run() у класі Thread не виконує ніяких операцій. Необхідно або розширити клас Thread, щоб включити в нього новий метод run(), або створити об'єкт Runnnable і передати його конструктору потоку. Коли створюється клас, що реалізовує інтерфейс Runnable, цей клас повинен перевизначити метод run(). Саме цей метод виконує фактичну роботу, покладену на конкретний потік.
· Створити потік можна за допомогою одного з наступних конструкторів:
· public Thread()
· public Thread(String name)
public Thread(Runnable target)
· public Thread(Runnable target, String name)
public Thread(ThreadGroup group, String name)
· public Thread(ThreadGroup group, Runnable target)
· public Thread(ThreadGroup group, Runnable target, String name)
У першому конструкторі створюється потік, який використовує самого себе як такий інтерфейс Runnable. У решті конструкторів використовувані параметри мають наступний сенс:
name - ім'я, яке привласнюється новому потоку;
· target - визначення цільового об'єкту, який використовуватиметься новим об'єктом Thread при запуску потоків. Якщо опустити цей параметр або привласнити йому значення null, новий об'єкт Thread запускатиме потоки за допомогою виклику методу run() поточного об'єкту Thread. Якщо при створенні нового об'єкту Thread указується цільовий об'єкт, то для запуску нових процесів він викликатиме метод run() вказаного цільового об'єкту;
· group - призначений для приміщення нового об'єкту Thread в дерево об'єктів даного класу. Якщо опустити даний параметр або привласнити йому значення null, новий об'єкт класу Thread стане членом поточної групи потоків ThreadGroup.
Метод public String toString() повертає рядкове представлення потоку, включаючи ім'я потоку, пріоритет і ім'я групи, а методи public final String getName() і public final void setName(String name) дозволяють одержати ім'я потоку або встановити ім'я потоку.
· Запуск потоку виконує метод public void start() throws IllegalThreadStateException (виключення кидається, якщо робиться спроба запуску вже запущеного потоку).
· Для зупинки потоку рекомендується потоку, що зупиняється, привласнити значення null, наприклад:
· Thread myThread;
· myThread.start(); // Запуск потоку
· myThread = null; // Зупинка або завершення потоку
· Припустимо, що на якомусь етапі роботи над програмним проектом необхідні два модулі, над якими працюють дві групи програмістів. Етап може початися, тільки якщо обидві групи закінчили роботу над своїми модулями, тобто однієї з груп програмістів доведеться чекати закінчення роботи над модулем іншої групи. Для такого узгодження дій використовується public final void join() throws InterruptedException.
· Методу join можна також передати значення тайм-ауту, використовуючи його варіанти public final syncronized void join(long millis) throws InterruptedException і public final syncronized void join(long millis, int nanos) throws InterruptedException. Ці методи чекають протягом millis мілісекунд або millis мілісекунд плюс nanos наносекунд.
· Якщо потрібний, щоб перед продовженням роботи потік чекав певний час, можна використовувати метод public static void sleep(long millis) throws InterruptedException або public static void sleep(long millis, int nanos) throws InterruptedException, де параметри millis і nanos мають той же сенс, що і для методу join. Метод sleep() дуже часто використовується в циклах, що управляють анімацією.
· Якщо в програмі є потік, який захоплює процесор, проводячи велику кількість обчислень, може з'явитися необхідність примушувати його час від часу "відпускати" процесор, даючи можливість виконуватися іншим потокам. Це досягається за допомогою методу public static void yield().
· Метод public void destroy() знищує потік без жодного очищення даних, що відносяться до нього, а метод public final boolean isAlive() дозволяє визначити, чи запущений потік і ще ЀживийЀ.
· Потоки в Java можуть переривати один одного. Механізм переривань реалізується за допомогою наступних методів:
public void interrupt() - перериває даний потік;
· public static boolean interrupted() - перевіряє, чи був перерваний даний потік (цей метод очищає ознаку переривання для потоку, тобто при повторному виклику методу для цього ж перерваного потоку він поверне значення false);
· public boolean isInterrupted() - аналогічний попередньому методу, але не очищає ознаки переривання для потоку.
У класі Thread є ряд статичних методів для вивчення поточного потоку і інших потоків з тієї ж групи. Метод public static Thread currentThread() повертає об'єкт, відповідний виконуваному у момент виклику потоку, метод public static void dumpStack() виводить трасування стека для поточного потоку.
· Метод public final void checkAccess() перевіряє, чи має право поточний потік модифікувати даний потік.
· Звичайно програма на Java працює до завершення всіх вхідних в неї потоків. Іноді, проте, зустрічаються потоки, що працюють у фоновому режимі, виконуючи допоміжні дії, які ніколи не закінчуються. Можна помітити такий потік як потік-демон (daemon thread), що говорить JVM про те, що цей потік не треба враховувати при визначенні, чи всі потоки даної програми завершилися. Іншими словами, додаток на Java виконується до тих пір, поки не завершиться останній потік, що не є демоном. Потоки, не помічені як демони, називаються призначеними для користувача потоками (user threads).
· Щоб потік вважався демоном, треба скористатися методом public final void setDaemon(boolean on) throws IllegalThreadStateException. Якщо параметр on рівний true, потік одержує статус демона, якщо false - статус призначеного для користувача потоку. Статус потоку може бути змінений в процесі його виконання. Метод public final boolean isDaemon() повертає true, якщо потік є демоном, і false, якщо це призначений для користувача потік.
· Метод public static int enumerate(Thread[] threadArray) класу Thread заповнює масив об'єктами Thread, що представляють потоки в групі, до якої відноситься поточний потік. Оскільки перед таким викликом необхідно створити масив threadArray, треба знати, скільки елементів буде одержано. Метод public static int activeCount() класу Thread повідомляє, скільки активних потоків в групі, до якої відноситься даний потік.
2.2. ПРІОРИТЕТИ І ГРУПИ ПОТОКІВ
Розподіли процесорного часу між потоками в Java виконується за наступними правилами: коли потік блокується, тобто припинений, переходить в стан очікування або повинен дочекатися якоїсь події, Java вибирає інший потік з тих, які готові до виконання. Вибирається потік, що має найбільший пріоритет. Якщо таких декілька, вибирається будь-якій з них. Пріоритет потоку можна встановити методом public final void setPriority(int newPriority) throws IllegalArgumentException.
Пріоритет потоку повинен бути числом в діапазоні від Thread.MIN_PRIORITY до Thread.MAX_PRIORITY. Будь-яке значення поза цими межами викликає виключення IllegalArgumentException. За умовчанням потоку приписується пріоритет Thread.NORM_PRIORITY. Значення пріоритету потоку можна з'ясувати за допомогою методу public final int getPriority().
Клас ThreadGroup реалізує стратегію забезпечення безпеки, яка дозволяє впливати один на одного тільки потокам з однієї групи. Наприклад, потік може змінити пріоритет іншого потоку з тієї ж групи або перевести його в стан очікування. Якби не було розбиття потоків на групи, один потік міг би викликати хаос в середовищі Java, перевівши в стан очікування всю решту потоків, або, що ще гірше, завершивши їх. Групи потоків організовані в ієрархічну структуру, де у кожної групи є батьківська група. Потоки можуть впливати на потоки з своєї групи і з дочірніх груп. Групу потоків можна створити, просто задавши її ім'я, за допомогою конструктора public ThreadGroup(String groupName).
· Можна також створити групу потоків, дочірню по відношенню до тієї, що існує, використовуючи конструктор public ThreadGroup (ThreadGroup existingGroup, String groupName) throws NullPointerException.
· Метод public String toString() повертає рядкове представлення даної групи, а методи public final String getName() і public final void setName(String name) дозволяють одержати ім'я групи або встановити ім'я групи.
· Інші методи класу ThreadGroup є аналогами відповідних методів класу Thread, але застосовуються до всієї групи:
public int activeCount() - повертає число активних потоків в даній групі;
· public int activeGroupCount() - повертає число активних груп в даній групі;
· public final void checkAccess() - перевіряє, чи може виконуваний в даний час потік модифікувати дану групу;
· public final void destroy() throws IllegalThreadStateException, SecurityException і public boolean isDestroyed() - відповідно знищує всі потоки даної групи і її підгруп (всі потоки в групі повинні бути заздалегідь зупинені) або перевіряє чи ЀживаЀ дана група;
· public final void interrupt() - перериває всі потоки в даній групі;
· public final boolean isDaemon() і public final void setDaemon(boolean daemon) - відповідно перевіряє і встановлює ознаку потоку-демона для всієї групи.
Можна обмежити пріоритет потоків з групи за допомогою виклику методу public final synchronized void setMaxPriority (int priority), а визначити максимальний пріоритет потоку в групі можна за допомогою методу public final int getMaxPriority().
· Батьківська група потоків доступна за допомогою виклику методу public final ThreadGroup getParent(), а за допомогою методу public final boolean parentOf(ThreadGroup g) можна перевірити чи є група g батьківської для даної групи.
2.3 СИНХРОНІЗАЦІЯ ПОТОКІВ
Як вже указувалося, основна відмінність потоків від процесів полягає в тому, що потоки не захищені один від одного засобами операційної системи. Тому будь-який з потоків може дістати доступ і навіть внести зміни в дані, які інший потік вважає своїми. Рішення цієї проблеми полягає в синхронізації потоків.
· Синхронізація потоків полягає в гарантії в кожен момент часу надання доступу до даних тільки до одного потоку. Для цього використовується наступний оператор:
synchronized (вираз)
оператор
Узятий в дужки вираз повинен указувати на дані, що блокуються, - звичайно воно є посиланням на об'єкт. Найчастіше при блокуванні об'єкту необхідно виконати відразу декілька операторів, так що оператор, як правило, є блоком.
· Якщо виявляється, що критична ділянка розповсюджується на весь метод, а ресурсом, що розділяється, є весь об'єкт в цілому, то можна просто вказати модифікатор synchronized в оголошенні методу, наприклад:
synchronized void myMethod()
{
// Тіло методу
}
Гнучкіший і ефективніший спосіб координації виконання потоків забезпечують методи wait() і notify() класу Object.
· Метод wait() переводить потік в стан очікування виконання певної умови і викликається за допомогою однієї з наступних форм:
public final void wait() throws InterruptedException, IllegalMonitorStateException - припинення виконання поточного потоку до отримання сповіщення;
· public final void wait(long timeout) throws InterruptedException, IllegalMonitorStateException, IllegalArgumentException - припинення виконання поточного потоку до отримання сповіщення або до закінчення заданого інтервалу часу timeout (у мілісекундах);
· public final void wait(long timeout, int nanos) throws InterruptedException, IllegalMonitorStateException, IllegalArgumentException - припинення виконання поточного потоку до отримання сповіщення або до закінчення заданого інтервалу часу timeout (у мілісекундах) і, додатково, в наносекундах (значення в діапазоні 0-999999).
Метод public final void notify() переводить в активний стан один з потоків, встановлених в стан очікування за допомогою методу wait(). Критерій вибору потоку є довільним і залежить від конкретної операційної системи і реалізації віртуальної машини Java. Якщо необхідно перевести всі чекаючі потоки в активний стан, можна скористатися методом public final void notifyAll().
· Методи можуть викликатися тільки потоком, який є власником монітора даного об'єкту. Якщо до виклику методів wait() або notify() не виконати захоплення монітора даного об'єкту, генерується виключення IllegalMonitorStateException. Потік стає власником даного об'єкту одним з трьох способів:
· викликом методу, оголошеного як synchronized, який здійснює доступ до необхідного екземпляра об'єкту класу Object;
· виконанням тіла оператора synchronized, призначеного для синхронізації доступ до необхідного екземпляра об'єкту класу Object;
· виконанням, для об'єктів типу Class, синхронізованого статичного методу цього класу.
Індивідуальне завдання
Напишіть програму моделювання з використанням на мові Java по одному з приведених нижче варіантів.
У програмах клас CPUQueue описує чергу, клас CPUProcess моделює потік процесів, а клас CPU - потік обслуговування процесу центральним процесором. Черги, потоки процесів і обслуговування процесу моделюються за допомогою об'єктів відповідного класу. Параметри черги моделюються за допомогою алгоритмів вставки і витягання процесу з черги. Параметром процесу є інтервал часу між двома послідовними генераціями процесів. Параметром процесора є час обслуговування процесу.
Випадкові часи для інтервалів між моментами генерації процесів і для часів обслуговування розподілені по рівномірному закону із заданими верхньою і нижньою межами (див. метод random() у класі Math). Початковими даними для моделювання є кількість процесів, які повинні згенерувати (для кожного потоку процесів), а також нижні і верхні межі для потоків.
Варіант 19. Програма моделює обслуговування двох потоків процесів з різними параметрами двома центральними процесором комп'ютера. Для кожного потоку задається своя черга. Процес з кожного потоку поступає на свій процесор, і лише у тому випадку, коли в своїй черзі немає процесу, процесор бере на обробку чужий процес. Визначте максимальний розмір для кожної з черг.
Код програми
import java.util.*;
import java.io.*;
public class lab3
{
static boolean flag1=true,flag2=true;
static Random Generator = new Random();
static public class Proces
{
private long sum=0;
private String name = new String();
public Proces(String Pname)
{
name = Pname;
}
public void runProces(String name2)
{
System.out.println(name2+": Process "+name+" Start");
for(int i=0;i<65000;i++)sum+=i;
System.out.println(name2+": Process "+name+" Complete");
}
}
static LinkedList<Proces> CPU1Queue = new LinkedList<Proces>();
static LinkedList<Proces> CPU2Queue = new LinkedList<Proces>();
static public class CPUProcess extends Thread
{
String name=new String();
LinkedList<Proces> CPUQueue;
boolean flag;
public CPUProcess(String ThreadName,LinkedList<Proces> CPUQueue_,boolean flag_)
{
super(ThreadName);
name = ThreadName;
CPUQueue = CPUQueue_;
flag = flag_;
start();
}
public void run()
{
try
{
int j = Generator.nextInt(10);
for(int i=0;i<j;i++)
{
Proces PR = new Proces(name+i);
CPUQueue.offer(PR);
System.out.println("Create Process "+name+i);
sleep((long)Generator.nextInt(100));
}
flag = false;
}
catch(InterruptedException ie){}
catch(ClassCastException CCE){};
}
}
static public class CPU extends Thread
{
String name=new String();
LinkedList<Proces> CPUQueue1;
LinkedList<Proces> CPUQueue2;
public CPU(String ThreadName,LinkedList<Proces> CPUQueue1_,LinkedList<Proces> CPUQueue2_)
{
super(ThreadName);
name = ThreadName;
CPUQueue1=CPUQueue1_;
CPUQueue2=CPUQueue2_;
start();
}
public void run()
{
try
{
for(;flag1&&flag2;)
{
Proces tmp = CPUQueue1.poll();
if(tmp == null)tmp = CPUQueue2.poll();
if(tmp != null)tmp.runProces(name);
else break;
sleep(500);
}
}
catch(InterruptedException io){};
}
}
public static void main(String[] args)
{
CPUProcess CPUProcess1 = new CPUProcess("First",CPU1Queue,flag1);
CPUProcess CPUProcess2 = new CPUProcess("Second",CPU2Queue,flag2);
CPU CPU1 = new CPU("CPU1",CPU1Queue,CPU2Queue);
CPU CPU2 = new CPU("CPU2",CPU2Queue,CPU1Queue);
}
}
Результати виконання
--------------------Configuration: lab3 - JDK version 1.6.0_16 <Default> - <Default>--------------------
Create Process Second0
Create Process First0
CPU1: Process First0 Start
CPU2: Process Second0 Start
CPU1: Process First0 Complete
CPU2: Process Second0 Complete
Create Process Second1
Create Process First1
Create Process Second2
Create Process First2
Create Process Second3
Create Process First3
Create Process First4
Create Process Second4
CPU1: Process First1 Start
CPU2: Process Second1 Start
CPU1: Process First1 Complete
CPU2: Process Second1 Complete
CPU1: Process First2 Start
CPU2: Process Second2 Start
CPU1: Process First2 Complete
CPU2: Process Second2 Complete
CPU1: Process First3 Start
CPU2: Process Second3 Start
CPU1: Process First3 Complete
CPU2: Process Second3 Complete
CPU2: Process Second4 Start
CPU1: Process First4 Start
CPU2: Process Second4 Complete
CPU1: Process First4 Complete
Process completed.
Висновки
На цій лабораторній роботі я придбав навики роботи з потоками при програмуванні на мові Java.