Взаимодействие между потоками. Методы wait(), notify(), notifyAll(). Примеры
Перед изучением данной темы рекомендуется ознакомиться со следующей темой:
Содержание
- 1. Средства взаимодействия между потоками выполнения. Методы wait(), notify(), notifyAll()
- 2. Схема (рисунок) взаимодействия между двумя параллельными потоками
- 3. Реализация системы опроса между потоками выполнения. Пример обмена значениями между двумя потоками, которые выполняются параллельно
- 4. Пример последовательного синхронизированного использования общего массива чисел между потоками. Реализация системы опроса
- Связанные темы
Поиск на других ресурсах:
1. Средства взаимодействия между потоками выполнения. Методы wait(), notify(), notifyAll()
Для обеспечения взаимодействия между потоками выполнения в классе Object реализованы 3 метода.
1. Метод wait() — переводит вызывающий поток в состояние ожидания. В этом случае вызывающий поток освобождает монитор, который имеет доступ к ресурсу. Ожидание продолжается до тех пор, пока другой поток выполнения, который вошел в монитор, не вызовет метод notify().
Согласно документации Java общая наиболее распространенная реализация метода следующая:
public final void wait() throws java.lang.InterruptedException;
В языке Java метод wait() имеет другие перегружены реализации, которые позволяют ожидать указанный период времени.
2. Метод notify() — возобновляет выполнение потока, из которого был вызван метод wait() для того же объекта.
Согласно документации Java общая форма метода следующая:
public final native void notify();
3. Метод notifyAll() — возобновляет выполнение всех потоков, из которых был вызван метод wait() для того же объекта. Управление передается одному из этих потоков.
Общая форма метода следующая:
public final native void notifyAll();
⇑
2. Схема (рисунок) взаимодействия между двумя параллельными потоками
На рисунке изображено взаимодействие между двумя потоками, которые выполняются параллельно. Потоки обмениваются экземпляром класса Value, который сохраняет некоторое значение. Каждый поток представлен классом. Класс, формирующий экземпляр Value называется PutValue. Класс, считывающий экземпляр Value называется GetValue.
Сначала формируется (записывается) некоторое значение экземпляра Value потоком PutValue. В этот момент поток GetValue ожидает сообщение от потока PutValue с помощью циклического вызова метода wait(). После того, как экземпляр класса Value сформирован, поток PutValue вызывает метод notify(). В результате управление передается потоку GetValue который должен прочитать значение. Теперь уже поток PutValue ожидает (метод wait()). Как только поток GetValue завершит считывание, он сообщает об этом путем вызова метода notify().
Процесс синхронизированного взаимодействия между потоками может повторяться сколько угодно.
Рисунок 1. Взаимодействие между параллельными потоками PutValue и GetValue, которые обмениваются общим ресурсом Value
⇑
3. Реализация системы опроса между потоками выполнения. Пример обмена значениями между двумя потоками, которые выполняются параллельно
Условие задачи. Реализовать обмен значением между двумя потоками, которые выполняются параллельно. Первый поток формирует некоторое случайное число. Второй поток ожидает, пока число не будет сформировано первым потоком. Затем это число передается во второй поток и там выводится. После этого управление вновь передается первому потоку для формирования следующего случайного числа. Процесс повторяется заданное количество раз.
Решение. Для решения данной задачи нужно сформировать 3 класса:
- класс Number, который определяет число используемое для обмена между потоками;
- класс FormNumber — представляет поток, формирующий случайные числа, которые будут передаваться в поток GetNumber;
- класс GetNumber — представляет поток, получающий числа из потока FormNumber.
// Класс, реализующий методы, которые синхронизированно получают и возвращают число class Number { int n; // флажок, определяющий параллельный ли поток // сформировал/получил число boolean valueSet = false; // Метод, возвращающий число synchronized int get() { while (!valueSet) { try { wait(); } catch (InterruptedException e) { System.out.println(e.getMessage()); } } valueSet=false; notify(); return n; } // Метод, который используется для передачи числа другому потоку synchronized void put(int n) { // Ожидать, пока не будет получено (valueSet=false) while(valueSet) { try { wait(); } catch(InterruptedException e) { System.out.println(e.getMessage()); } } this.n = n; valueSet=true; notify(); } } // Класс, представляющий поток, который формирует число class FormNumber implements Runnable { Number num; // Формируемое число int count; // количество чисел, которые нужно отправить другому потоку Thread thread; // Ссылка на текущий поток // Конструктор - получает число и количество сформированных чисел FormNumber(Number num, int count) { // Заполнить внутренние переменные this.num = num; this.count = count; // Создать поток и запустить его на выполнение thread = new Thread(this, "FormNumber"); thread.start(); } // Метод, реализующий код выполнения потока public void run() { for (int i=0; i<count; i++) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } // Сформировать некоторое случайное число от 1 до 100 int number = (int)(Math.random()*100+1); System.out.println("Поток: " + thread.getName() + ", сформировано число: " + number); num.put(number); } } } class GetNumber implements Runnable { // Внутренние переменные Number num; // формируемое число int count; // количество чисел, которые нужно получить Thread thread; // ссылка на текущий поток GetNumber(Number num, int count) { // Заполнить внутренние переменные this.num = num; this.count = count; // Создать поток и запустить его на выполнение thread = new Thread(this, "GetNumber"); thread.start(); } // Метод, который содержит код выполнения потока public void run() { for (int i=0; i<count; i++) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Поток: " + thread.getName() + ", получено число " + num.get()); } } } public class Threads { public static void main(String[] args) { // Тест работы потоков Number num = new Number(); new FormNumber(num, 5); // Передать в другой поток 5 чисел new GetNumber(num, 5); // Получить из другого потока 5 чисел } }
Результат выполнения программы
Поток: FormNumber, сформировано число: 26 Поток: GetNumber, получено число 26 Поток: FormNumber, сформировано число: 10 Поток: GetNumber, получено число 10 Поток: FormNumber, сформировано число: 88 Поток: GetNumber, получено число 88 Поток: FormNumber, сформировано число: 58 Поток: GetNumber, получено число 58 Поток: FormNumber, сформировано число: 70 Поток: GetNumber, получено число 70
⇑
4. Пример последовательного синхронизированного использования общего массива чисел между потоками. Реализация системы опроса
Решение данного примера можно использовать в качестве шаблона для реализации взаимодействия между собственно разработанными потоками.
Условие задачи. Реализовать доступ к массиву поочередно из двух потоков. Первый поток формирует массив чисел указанной длины. Второй поток считывает этот массив чисел. Второй поток ожидает, когда произойдет формирование массива чисел первым потоком. Точно также первый поток должен ожидать до тех пор, пока второй поток не завершит чтение массива.
Продемонстрировать взаимодействие потоков выполнения указанное количество раз.
Решение. Для решения задачи нужно реализовать 3 класса:
- Array — определяет массив целых чисел заданного размера;
- FormArray — предназначен для формирования массива случайных чисел произвольного размера;
- GetArray — используется для получения массива чисел, предварительно сгенерированных в классе FormArray.
В классе Array реализованы следующие члены класса:
- AI — массив целых чисел. Этот массив является общим ресурсом для потоков, которым соответствуют классы FormArray и GetArray;
- formArray — флажок типа boolean, который используется для определения потоками периода ожидания;
- конструктор Array(), который получает параметром размер массива. В конструкторе выделяется память для массива AI;
- метод get() — используется потоком GetArray для считывания массива AI;
- метод set() — используется потоком FormArray для формирования массива целых чисел;
- метод getArray() — ссылка на скрытый (private) массив AI.
Оба класса FormArray и GetArray реализуют интерфейс Runnable. Классы инкапсулируют потоки выполнения. В классах реализованы следующие составляющие элементы:
- A — экземпляр (объект) класса Array. Этот экземпляр сохраняет массив целых чисел;
- count — целочисленное значение, которое определяет количество итераций при выполнении каждой из которых будет сформирован новый массив;
- конструктор;
- метод run() содержащий код потока выполнения. Для класса FormArray этот метод содержит код формирования массива целых чисел с помощью вызова метода put() экземпляра A. В методе run() класса GetArray вызывается метод get() экземпляра A для чтения массива, сформированного в параллельном потоке FormArray.
// Класс, реализующий массив с синхронизированными методами get(), set(). class Array { // Скрытые внутренние поля класса private int[] AI; private boolean formArray; // Конструктор - получает извне размер массива Array(int size) { // Сформировать массив AI = new int[size]; formArray = false; } // Метод синхронизированного чтения массива synchronized int[] get() { // ожидать, пока не будет сформирован массив while (!formArray) { try { wait(); } catch (InterruptedException e) { System.out.println(e.getMessage()); } } // Изменить флажок взаимодействия formArray = false; // Сообщить другой поток, что массив получен notify(); // Вернуть результат return AI; } // Метод синхронизированной записи массива чисел synchronized void put(int[] _AI) { // Ожидать, пока не будет прочитан массив (formArray=false) while(formArray) { try { wait(); } catch(InterruptedException e) { System.out.println(e.getMessage()); } } // Записать массив в общий ресурс AI = _AI; // Изменить флажок взаимодействия между потоками formArray = true; // Сообщить другому потоку, что массив сформирован notify(); } // Метод доступа к массиву AI int[] getArray() { return AI; } } // Класс, реализующий поток, который формирует массив случайных чисел class FormArray implements Runnable { // Внутренние поля класса private Array A; // собственно и массив private int count; // количество итераций формирования массива // Конструктор. // Параметры: // - A - ссылка на общий массив; // - count - количество итераций, формирующих массив FormArray(Array A, int count) { // Заполнить внутренние переменные this.A = A; this.count = count; // Создать поток new Thread(this, "FormArray").start(); } // Непосредственное выполнение потока public void run() { // count - количество итераций формирующих массив for (int k = 0; k<count; k++) { try { // Задержка на 1 секунду Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // сформировать случайный массив со случайными числами int[] AI = new int[A.getArray().length]; // записать в массив случайные числа for (int i=0; i<AI.length; i++) AI[i] = (int)(Math.random()*AI.length+1); // Вывести сформированный массив System.out.println("Сформовано: "); for (int i=0; i<AI.length; i++) System.out.print(AI[i] + " "); System.out.println(); // записать массив в общий синхронизированный ресурс A.put(AI); } } } // Класс, реализующий поток, в котором считывается массив целых чисел. // Массив целых чисел предварительно сформирован в экземпляре класса FormArray class GetArray implements Runnable { // Внутренние члены данных класса private Array A; private int count; // Конструктор. Получает 2 параметра: // - _A - массив, который есть общим ресурсом для двух потоков; // - count - количество итераций (шагов) считывания массива из параллельного потока. GetArray(Array _A, int count) { // Заполнить внутренние переменные A = _A; this.count = count; new Thread(this, "GetArray").start(); } // Код выполнения потока public void run() { for (int k=0; k<count; k++) { try { // Задержка на 0.2 секунды Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } // Получить массив чисел из синхронизированного потока int[] AI = A.get(); // Вывести полученный массив System.out.println("Получено: "); for (int i=0; i<AI.length; i++) System.out.print(AI[i] + " "); System.out.println(); } } } public class Threads { public static void main(String[] args) { Array A = new Array(10); new FormArray(A, 6); new GetArray(A, 6); } }
⇑
Связанные темы
- Многозадачность. Потоки выполнения. Основные понятия
- Средства Java для работы с потоками выполнения. Класс Thread. Интерфейс Runnable. Главный поток выполнения. Создание дочернего потока
- Методы класса Thread: getName(), run(), start(), sleep(). Примеры
- Методы класса Thread: join(), isAlive(), getPriority(), setPriority(). Примеры
- Синхронизация потоков. Монитор. Общие понятия. Ключевое слово synchronized
- Состояния потока выполнения. Метод getState(). Пример
⇑