Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Готовые отчеты (2020) / Java. Лабораторная работа 7

.pdf
Скачиваний:
58
Добавлен:
29.01.2021
Размер:
1.04 Mб
Скачать

Федеральное агентство связи ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ

ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ «САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ТЕЛЕКОММУНИКАЦИЙ ИМ. ПРОФ. М. А. БОНЧ-БРУЕВИЧА» (СПбГУТ)

Факультет инфокоммуникационных сетей и систем Кафедра программной инженерии и вычислительной техники

ЛАБОРАТОРНАЯ РАБОТА №7

по дисциплине «Разработка Java-приложений управления телекоммуникациями»

Выполнил: студент 3-го курса дневного отделения группы ИКПИ-85

Коваленко Леонид Александрович Преподаватель:

доцент кафедры ПИиВТ Белая Татьяна Иоанновна

Санкт-Петербург

2020

Цель работы Ознакомиться с общими принципами создания многопоточных

приложений.

Ход работы

Задание №1.

Создадим два класса потоков WriteThread и ReadThread (наследуются от класса Thread), взаимодействующих с помощью промежуточного объекта типа IVector (табл. 1). Первый поток последовательно заполняет вектор (изначально он заполнен нулями) случайными величинами. Каждый раз при помещении значения в вектор первый поток выводит на экран сообщение вида «Write: 100.5 to position 3». По достижении конца вектора заканчивает свое выполнение. Второй поток последовательно считывает значения из вектора и выводит их на экран сообщениями вида «Read: 100.5 from position 3». По достижении конца вектора заканчивает свое выполнение.

Таблица 1 — Код Task7Part1.java

import java.lang.Thread; import vectors.IVector; import vectors.Array;

import vectors.CircularLinkedList;

public class Task7Part1 {

public static void

main(String[] args) throws Exception {

IVector arr1 =

new Array(2);

IVector

arr2

=

new

Array(3);

IVector

list

=

new

CircularLinkedList(4);

System.out.print("[Test] arr1: "); arr1.print(); WriteThread wThread = new WriteThread(arr1); ReadThread rThread = new ReadThread(arr1); wThread.start();

rThread.start();

wThread.join();

rThread.join();

System.out.print("[Test] arr2: "); arr2.print(); wThread = new WriteThread(arr2);

rThread = new ReadThread(arr2); wThread.start(); rThread.start(); wThread.join(); rThread.join();

System.out.print("[Test] list: "); list.print(); wThread = new WriteThread(list);

rThread = new ReadThread(list); wThread.start(); rThread.start(); wThread.join(); rThread.join();

2

}

public static class WriteThread extends Thread { public WriteThread(IVector vector) {

this.vector = vector;

}

public void run() { try {

for (int i = 0, size = vector.size(); i < size; ++i) { double d = Math.random();

vector.set(i, d);

System.out.println("Write: " + d + " to position " + i);

}

}

catch(Exception e) { e.printStackTrace();

}

}

private final IVector vector;

}

public static class ReadThread extends Thread { public ReadThread(IVector vector) {

this.vector = vector;

}

public void run() { try{

for (int i = 0, size = vector.size(); i < size; ++i) { System.out.println("Read: " + vector.get(i) + " from

position " + i);

}

}

catch(Exception e) { e.printStackTrace();

}

}

private final IVector vector;

}

}

Результат компиляции и запуска приведен на рис. 1.

Рисунок 1 — Компиляция и запуск Task7Part1.java

3

Как можно заметить, потоки работают независимо друг от друга. В первом случае сначала произошло чтение, а затем запись (хотя в коде первым запускался поток записи), в других случаях иначе.

Обычно пишут: «Потоки работают параллельно». Однако это чаще всего (на уровне команд) не так: неизвестно, когда готовый к исполнению поток начнет работу, сколько времени он отработает, какой поток работал перед ним и какой поток будет работать после него. Т. е. потоки обычно выполняются не по очереди FIFO («first in, first out»).

Кроме того, зачастую на современных компьютерах код ради скорости выполняется не в том порядке, в котором написан. Неявная перестановка выполняется компилятором, процессором и подсистемой памяти.

Любой поток может быть запланирован для выполнения на отдельном ядре ЦП, использовать квантование времени на одноядерном процессоре или использовать квантование времени на нескольких процессорах. В последних двух случаях система будет периодически переключаться между потоками, поочередно давая выполняться то одному, то другому потоку. Такая схема называется псевдо-параллелизмом. Нет универсального решения, которое сказало бы как именно потоки Java будут преобразованы в нативные («врожденные») потоки ОС. Это зависит от конкретной реализации JVM.

Потоки нужны тогда, когда важна скорость работы или необходимо параллельное выполнение нескольких задач.

Для того, чтобы потоками можно было управлять, придуманы специальные механизмы синхронизации.

Задание №2.

Создадим два новых класса потоков SequentalWriter и SequentalReader (реализуют интерфейс Runnable), обеспечивающих последовательность операций чтения-записи (т. е. на экран сообщения выводятся в порядке write- read-write-read-...) независимо от приоритетов потоков. Для этого опишем вспомогательный класс Keeper, объект которого будем использовать при взаимодействии потоков.

Код приведен в табл. 2.

4

Таблица 2 — Код Task7Part2.java

import java.lang.Thread; import vectors.IVector; import vectors.Array;

import vectors.CircularLinkedList;

import vectors.VectorIndexOutOfBoundsException;

public class Task7Part2 {

public static void main(String[] args) throws Exception { IVector arr1 = new Array(2); System.out.print("[Test] arr1: "); arr1.print(); Keeper keeperArr1 = new Keeper(arr1);

SequentalWriter seqWriter = new SequentalWriter(keeperArr1); SequentalReader seqReader = new SequentalReader(keeperArr1); Thread thread1 = new Thread(seqWriter);

Thread thread2 = new Thread(seqReader); thread1.start();

thread2.start();

thread1.join();

thread2.join();

IVector arr2 = new Array(3); System.out.print("[Test] arr2: "); arr2.print(); Keeper keeperArr2 = new Keeper(arr2);

seqWriter = new SequentalWriter(keeperArr2); seqReader = new SequentalReader(keeperArr2); thread1 = new Thread(seqWriter);

thread2 = new Thread(seqReader); thread1.start(); thread2.start(); thread1.join();

thread2.join();

IVector list = new CircularLinkedList(4); System.out.print("[Test] list: "); list.print(); Keeper keeperList = new Keeper(list);

seqWriter = new SequentalWriter(keeperList); seqReader = new SequentalReader(keeperList); thread1 = new Thread(seqWriter);

thread2 = new Thread(seqReader); thread1.start(); thread2.start(); thread1.join();

thread2.join();

}

// Класс, хранящий вектор и флаг записи public static class Keeper {

public volatile boolean isWrite; public Keeper(IVector vector) {

this.vector = vector; this.isWrite = true;

}

public void set(int i, double d) throws VectorIndexOutOfBoundsException {

vector.set(i, d);

}

public double get(int i) throws VectorIndexOutOfBoundsException { return vector.get(i);

}

public int size() { return vector.size();

}

private final IVector vector;

5

}

// Класс для последовательной записи значений в вектор public static class SequentalWriter implements Runnable {

public SequentalWriter(Keeper keeper) { this.keeper = keeper;

}

public void run() { try {

for (int i = 0, size = keeper.size(); i < size; ++i) { double d = Math.random();

synchronized(keeper) {

while (!keeper.isWrite) { keeper.wait();

}

keeper.set(i, d);

System.out.println("Write: " + d + " to position " +

i);

keeper.isWrite = false; keeper.notify();

}

}

} catch (Exception e) { e.printStackTrace();

}

}

private final Keeper keeper;

}

// Класс для последовательного считывания значений из вектора public static class SequentalReader implements Runnable {

public SequentalReader(Keeper keeper) { this.keeper = keeper;

}

public void run() { try {

for (int i = 0, size = keeper.size(); i < size; ++i) { synchronized(keeper) {

while (keeper.isWrite) { keeper.wait();

}

System.out.println("Read: " + keeper.get(i) + " from

position " + i);

keeper.isWrite = true; keeper.notify();

}

}

} catch (Exception e) { e.printStackTrace();

}

}

private final Keeper keeper;

}

}

6

Результат компиляции и запуска представлен на рис. 2.

Рисунок 2 — Компиляция и запуск Task7Part2.java

Важное замечание. Данная реализация предполагает использование только одного потока записи и одного потока чтения. Таким образом, возникает вопрос вообще о целесообразности использования механизмов синхронизации при заранее последовательной логике работы: запись-чтение- запись-чтение-… Поэтому предлагается другое решение, при котором можно использовать несколько потоков чтения и один поток записи (табл. 3).

Таблица 3 — Код Task7Part2Ex.java

import java.lang.Thread; import vectors.IVector; import vectors.Array;

import vectors.CircularLinkedList;

import vectors.VectorIndexOutOfBoundsException;

public class Task7Part2Ex {

public static void main(String[] args) throws Exception { IVector arr = new Array(2);

System.out.print("[Test | 3 Readers] arr: "); arr.print(); Keeper keeperArr = new Keeper(arr, 3); // 3 потока чтения SequentalWriter seqWriter = new SequentalWriter(keeperArr); SequentalReader seqReader = new SequentalReader(keeperArr); SequentalReader seqReader2 = new SequentalReader(keeperArr); Thread thread1 = new Thread(seqWriter, "Writer");

Thread thread2 = new Thread(seqReader, "Reader 1"); Thread thread3 = new Thread(seqReader, "Reader 2"); Thread thread4 = new Thread(seqReader2, "Reader 3"); thread1.start();

thread2.start();

thread3.start();

thread4.start();

thread1.join();

thread2.join();

thread3.join();

thread4.join();

7

IVector list = new CircularLinkedList(4); System.out.print("[Test | 3 Readers] list: "); list.print(); Keeper keeperList = new Keeper(list, 3); // 3 потока чтения seqWriter = new SequentalWriter(keeperList);

seqReader = new SequentalReader(keeperList); seqReader2 = new SequentalReader(keeperList); thread1 = new Thread(seqWriter, "Writer"); thread2 = new Thread(seqReader, "Reader 1"); thread3 = new Thread(seqReader, "Reader 2"); thread4 = new Thread(seqReader2, "Reader 3"); thread1.start();

thread2.start();

thread3.start();

thread4.start();

thread1.join();

thread2.join();

thread3.join();

thread4.join();

}

//Класс, хранящий вектор и информацию для управления потоками

//maxPermits определяет число потоков, которые будут иметь доступ для

чтения

//Только один поток должен иметь доступ для записи

public static class Keeper {

public Keeper(IVector vector, int maxPermits) { this.vector = vector;

this.index = -1; this.current = 0; this.maxPermits = maxPermits;

}

public void set(int i, double d) throws VectorIndexOutOfBoundsException {

vector.set(i, d);

}

public double get(int i) throws VectorIndexOutOfBoundsException { return vector.get(i);

}

public int size() { return vector.size();

}

private volatile int current, index; private final int maxPermits; private final IVector vector;

}

// Класс для последовательной записи значений в вектор public static class SequentalWriter implements Runnable {

public SequentalWriter(Keeper keeper) { this.keeper = keeper;

}

public void run() { try {

Thread current = Thread.currentThread();

for (int i = 0, size = keeper.size(); i < size; ++i) { double d = Math.random();

synchronized(keeper) {

while (keeper.current > 0) { keeper.wait();

}

keeper.set(i, d);

System.out.println("(" + current.getName() + ") Write: " + d + " to position " + i);

keeper.current = keeper.maxPermits;

8

keeper.index = i; keeper.notifyAll();

}

}

} catch (Exception e) { e.printStackTrace();

}

}

private final Keeper keeper;

}

// Класс для последовательного считывания значений из вектора public static class SequentalReader implements Runnable {

public SequentalReader(Keeper keeper) { this.keeper = keeper;

}

public void run() { try {

Thread current = Thread.currentThread();

for (int i = 0, size = keeper.size(); i < size; ++i) { synchronized(keeper) {

while (keeper.index != i) { keeper.wait();

}

System.out.println("- (" + current.getName() + ")

Read: " + keeper.get(i) + " from position " + i);

--keeper.current; keeper.notifyAll();

}

}

} catch (Exception e) { e.printStackTrace();

}

}

private final Keeper keeper;

}

}

Результат компиляции и запуска представлен на рис. 3.

Рисунок 3 — Компиляция и запуск Task7Part2Ex.java

9

Данная реализация может быть изменена в сторону принципа единственной ответственности (табл. 4): три класса (SequentalWriter, SequentalReader и Keeper) содержат многопоточную логику, которую лучше всего вынести в один класс, например, ConcurrentKeeper, наследуемого от

(потоконебезопасного) Keeper. При этом SequentalWriter и SequentalReader

переименуем в VectorWriterRun и VectorReaderRun соответственно. Эти классы будут работать без многопоточной логики — они будут вызывать методы set и get экземпляра класса Keeper соответственно.

Таблица 4 — Код Task7Part2SRP.java

import java.lang.Thread; import vectors.IVector; import vectors.Array;

import vectors.CircularLinkedList;

import vectors.VectorIndexOutOfBoundsException;

public class Task7Part2SRP {

public static void main(String[] args) throws Exception { IVector arr = new Array(4);

System.out.print("[Keeper Test, without concurrency | 1 Reader] arr: "); arr.print();

Keeper keeperArr = new Keeper(arr);

VectorWriterRun vectorWriterRun = new VectorWriterRun(keeperArr); VectorReaderRun vectorReaderRun = new VectorReaderRun(keeperArr); vectorWriterRun.run();

vectorReaderRun.run();

arr = new Array(2);

System.out.print("[SomeConcurrentKeeper Test, with concurrency | 3 Readers] arr: "); arr.print();

keeperArr = new SomeConcurrentKeeper(arr, 3); vectorWriterRun = new VectorWriterRun(keeperArr); vectorReaderRun = new VectorReaderRun(keeperArr);

VectorReaderRun vectorReaderRun2 = new VectorReaderRun(keeperArr); Thread thread1 = new Thread(vectorWriterRun, "Writer");

Thread thread2 = new Thread(vectorReaderRun, "Reader 1"); Thread thread3 = new Thread(vectorReaderRun, "Reader 2"); Thread thread4 = new Thread(vectorReaderRun2, "Reader 3"); thread1.start();

thread2.start();

thread3.start();

thread4.start();

thread1.join();

thread2.join();

thread3.join();

thread4.join();

}

// Класс Keeper, хранящий вектор и три метода для работы с ним public static class Keeper {

public Keeper(IVector vector) { this.vector = vector;

}

public void set(int i, double d) throws VectorIndexOutOfBoundsException {

vector.set(i, d);

10