Базовые свойства многопоточности

Содержание

Слайд 2

Цикл, который загрузит процессор

public class AppDemo extends JApplet {
public AppDemo() {}
public

Цикл, который загрузит процессор public class AppDemo extends JApplet { public AppDemo()
void init() {
Container c = getContentPane();
c.setLayout(new FlowLayout());
JButton btn = new JButton("Начать вычисления");
btn.addActionListener(new ActionListener() {
for (int i = 0; i < longInteger; i++){
sum += values[i];

}
}
});
c.add(btn);
c.add(txt);
}
}
В таком примере после нажатия кнопки “Начать вычисления” приложение “подвиснет” до окончания метода обработки события.
Если выполняются объемные вычисления, то для нормальной работы интерфейса целесообразно выполнять вычисления в отдельном потоке

Слайд 3

Создание потока

Для создания потока в Java служит класс Thread
Существует 2 варианта создания

Создание потока Для создания потока в Java служит класс Thread Существует 2
потока:
путем наследования класса Thread
путем реализации интерфейса Runnable

Слайд 4

Создание потока – наследника Thread

Простейшим способом создания потока является создание класса, производного

Создание потока – наследника Thread Простейшим способом создания потока является создание класса,
от Thread, с переопределенным в нем методом run()
Далее следует создать объект созданного класса и вызвать его метод start()
Метод start() выполняет системные действия по созданию потока, после чего вызывает переопределенный нами метод run()
С этого момента начнет свое существование новый поток. Он будет выполняться параллельно с другими потоками, конкурируя с ними за время процессора.
Когда произойдет выход из метода run(), поток завершит свою работу

Слайд 5

Пример создания потока

Каждые полсекунды поток выводит сообщение на экран:
public class NewThread extends

Пример создания потока Каждые полсекунды поток выводит сообщение на экран: public class
Thread {
public void run() {
try {
for (int i = 5; i > 0; i-- ) {
System.out.println("Дочерний поток: " + i);
Thread.sleep(500);
}
} catch (InterruptedException ex) {
// Исключение может выбросить метод sleep()
System.out.println("Дочерний поток прерван.");
}
System.out.println("Дочерний поток завершен.");
}
}
Из главного потока создадим дочерний поток:
(new NewThread()).start();

Слайд 6

Создание потока путем реализации интерфейса Runnable

Этот способ применяется, когда наследование класса от

Создание потока путем реализации интерфейса Runnable Этот способ применяется, когда наследование класса
Thread невозможно (например, класс уже наследуется от другого класса)
Шаги, которые нужно выполнить:
Создать класс MyTread, реализующий интерфейс Runnable, и реализовать в нем метод run()
Создать экземпляр этого класса
Создать объект Thread, передав в конструктор экземпляр класса MyThread.
Вызвать метод start() объекта Thread

Слайд 7

Пример создания потока

public class MyThread implements Runnable {
public void run() {
// тело

Пример создания потока public class MyThread implements Runnable { public void run()
метода такое же,
// как в предыдущем примере
}
}
Из главного потока создадим дочерний поток:
MyTread m = new MyThread();
Thread t = new Thread(m);
t.start();

Слайд 8

Диспетчеризация потоков

Параллельная работа потоков на одном процессоре обеспечивается операционной системой
Для того,

Диспетчеризация потоков Параллельная работа потоков на одном процессоре обеспечивается операционной системой Для
чтобы обеспечить правильную передачу управления между потоками на любой ОС, поток должен периодически отдавать управление операционной системе.
Для этих целей в классе Thread служат 2 метода
sleep(long millis) – приостанавливает работу потока на заданное количество миллисекунд
yield() – не приостанавливает работу потока, а просто дает возможность ОС выполнить, при необходимости, переключение на другой поток или процесс
В любом методе run нужно использовать либо sleep , либо yield, либо какой-то другой их эквивалент (wait или методы чтения из файла). В противном случае можно получить эффект монопольного захвата ресурсов машины одной нитью

Слайд 9

Приоритеты потоков

Приоритеты потоков определяют порядок передачи управления между ними
Правила передачи управления следующие:
если

Приоритеты потоков Приоритеты потоков определяют порядок передачи управления между ними Правила передачи
поток добровольно отказался от управления, оно переходит к потоку с самым высоким приоритетом
если поток приостановлен потоком с более высоким приоритетом, то управление передается ему
если приоритеты потоков равные, то поведение зависит от операционной системы
Исходное значение приоритета потока совпадает с приоритетом потока-родителя.
Приоритет может быть изменен вызовом метода Thread.setPriority() с аргументом из интервала MIN_PRIORITY- MAX_PRIORITY
константы определены в классе Thread
По умолчанию приоритет потока равен NORM_PRIORITY

Слайд 10

Текущий поток

Ссылку на объект Thread текущего потока можно получить с помощью статического

Текущий поток Ссылку на объект Thread текущего потока можно получить с помощью
метода
Thread.currentThread()
public static void main(String[] args) {
// Распечатывает имя текущего потока
System.out.println(Thread.currentThread().getName());
}
Все статические методы класса Thread относятся именно к текущему потоку
Например, Thread.sleep(1000);

Слайд 11

Имена потоков

Потоку можно присвоить имя
либо с помощью аргумента конструктора,
либо методом

Имена потоков Потоку можно присвоить имя либо с помощью аргумента конструктора, либо
setName(String).
Текущее значение имени потока можно получить методом getName()
Имя потока не используется исполняющей системой и служит для удобства программиста
Поток обязан иметь имя. Если оно не задано программистом, автоматически вызываемый конструктор потока без аргументов установит его сам
По умолчанию главный поток имеет имя main, остальные – Thread-1, 2 и т.д.

Слайд 12

Потоки-демоны

Поток-демон, как правило, предоставляет некоторые общие услуги и работает в фоновом режиме,

Потоки-демоны Поток-демон, как правило, предоставляет некоторые общие услуги и работает в фоновом
пока работает программа
Незавершенные потоки-демоны разрушаются автоматически, когда заканчивается последний из пользовательских потоков
Для обеспечения "демоничности" потоков служат два метода класса Thread:
boolean isDaemon() – проверка, является ли демоном
setDaemon(boolean on) - устанавливает/снимает признак демона

Слайд 13

Проблема множественного доступа

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

Проблема множественного доступа Когда несколько потоков разделяют один ресурс, бывает необходимо синхронизировать
работу так, чтобы только один поток имел доступ к ресурсу в каждый момент времени
Доступ к ресурсу регулируется при помощи монитора
Монитор – это объект, который используется для взаимоисключающей блокировки потоков (mutually exclusive lock)
Когда один поток начинает выполнять любой из методов объекта-монитора (говорят, что поток входит в монитор), остальные, подойдя к монитору, останавливаются и ждут, пока монитор не освободится
В Java монитором может служить любой объект.
Эта способность передается по наследству от класса Object.

Слайд 14

Средства синхронизации потоков в Java

На уровне объекта можно синхронизировать
Метод. При вызове такого

Средства синхронизации потоков в Java На уровне объекта можно синхронизировать Метод. При
метода блокируется объект, для которого вызван данный метод
public void synchronized f() {
...
}
Участок кода.
synchronized(ref) { // ref – ссылка на блокируемый объект
...
}
Когда один из потоков входит в критический участок (synchronized метод или блок), связанный с блокировкой какого-то объекта, то остальные потоки не могут войти во все критические участки, связанные с блокировкой того же объекта, пока захвативший объект поток не выйдет из критического участка

Слайд 15

Пример синхронизации участков кода

public class MyObject{
// В данном классе объявлены 2 синхронизированных

Пример синхронизации участков кода public class MyObject{ // В данном классе объявлены
метода f() и g()
private String name;
public MyObject(String name){ this.name = name; }
public synchronized void f(){
System.out.println("f() started for object" + name);
try{
Thread.sleep(1000);
}catch(InterruptedException e){
throw new RuntimeException(e);
}
System.out.println("f() finished for object" + name);
}
public synchronized void g(){
System.out.println("g() started for object" + name);
try{
Thread.sleep(1000);
}catch(InterruptedException e){
throw new RuntimeException(e);
}
System.out.println("g() finished for object" + name);
}
}

Слайд 16

Пример синхронизации участков кода

// Поток, в котором вызывается метод f()
public class Thread1

Пример синхронизации участков кода // Поток, в котором вызывается метод f() public
extends Thread{
private MyObject myObj;
public Thread1(MyObject myObj){ this.myObj = myObj; }
public void run(){
myObj.f();
}
}
// Поток, в котором вызывается метод g()
public class Thread2 extends Thread{
private MyObject myObj;
public Thread2(MyObject myObj){ this.myObj = myObj; }
public void run(){
myObj.g();
}
}
// Обратите внимание, что объект MyObject передается данным потокам в качестве параметра

Слайд 17

Пример синхронизации участков кода

В данном потоке объявлена критическая секция, связанная с объектом

Пример синхронизации участков кода В данном потоке объявлена критическая секция, связанная с
MyObject
static class Thread3 extends Thread{
private MyObject myObj;
public Thread3(MyObject myObj){ this.myObj = myObj; }
public void run(){
synchronized(myObj){
System.out.println("synchonized section started for object" + myObj.name);
try{
Thread.sleep(1000);
}catch(InterruptedException e){
throw new RuntimeException(e);
}
System.out.println("synchonized section finished for object" + myObj.name);
}
}

Слайд 18

Пример синхронизации участков кода

public static void main(String[] args) {
MyObject myObj = new

Пример синхронизации участков кода public static void main(String[] args) { MyObject myObj
MyObject("MyObject1");
// Вызов потоков с синхронизацией по MyObject1
Thread1 t1 = new Thread1(myObj);
t1.start();
Thread2 t2 = new Thread2(myObj);
t2.start();
Thread3 t3 = new Thread3(myObj);
t3.start();
// Вызов потока с синхронизацией по MyObject2
MyObject myObj1 = new MyObject("MyObject2");
Thread1 t4 = new Thread1(myObj1);
t4.start();
}

Слайд 19

Пример синхронизации участков кода

Результаты запуска программы
f() started for object MyObject1
f() started for

Пример синхронизации участков кода Результаты запуска программы f() started for object MyObject1
object MyObject2
f() finished for object MyObject1
g() started for object MyObject1
f() finished for object MyObject2
g() finished for object MyObject1
synchonized section started for object MyObject1
synchonized section finished for object MyObject1
Как видно, все синхронизированные участки кода по объекту MyObject1, выполнены последовательно.
Участок, синхронизированный по MyObject2 – параллельно с остальными участками

Слайд 20

Синхронизация статических участков

В качестве синхронизирующего объекта может выступать сам класс
В этом случае

Синхронизация статических участков В качестве синхронизирующего объекта может выступать сам класс В
последовательно выполняются все участки кода, синхронизированные по этому классу
static class MyClass{
public synchronized static void f(){
System.out.println("f() started");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
throw new RuntimeException(e);
}
System.out.println("f() finished");
}
}
static class ThreadS extends Thread{
public void run(){
MyClass.f();
}
}

Слайд 21

Пример синхронизации статических участков

public static void main(String[] args) {
ThreadS ts = new

Пример синхронизации статических участков public static void main(String[] args) { ThreadS ts
ThreadS();
ts.start();
synchronized(MyClass.class){
System.out.println("We are in synchronized section");
}
}
Результат работы:
f() started
f() finished
We are in synchronized section
Как видно, участок, синхронизированный по MyClass.class ожидает, пока не выполнится метод f()

Слайд 22

Методы wait(), notify(), notifyAll()

Эти методы применяются для обеспечения взаимодействия между потоками при

Методы wait(), notify(), notifyAll() Эти методы применяются для обеспечения взаимодействия между потоками
работе с объектом
Они определены в классе Object, а значит, могут быть вызваны для всех объектов
wait() – блокирует выполнение потока, из которого он вызван, и одновременно разблокирует объект, для которого он вызван
notify() - пробуждает один из потоков, которые вызвали wait() на том же самом мониторе
notifyAll() – пробуждает все потоки. Первым будет выполняться поток с самым высоким приоритетом
Эти методы следует вызывать только когда объект служит монитором, т.е. из синхронизированного кода

Слайд 23

Применение wait () и notifyAll()

Типичный код для перевода потока в режим ожидания:
synchronized

Применение wait () и notifyAll() Типичный код для перевода потока в режим
void do() {
while (условие ожидания)
wait();
// Сделать нечто, когда условие ожидания снимется
}
Оператор while нельзя заменять оператором if, поскольку поток может просыпаться от уведомлений, не связанных с условием его ожидания.
Типичный код для вывода потоков из режима ожидания:
synchronized void changeCondition() {
// Изменить условие ожидания
notifyAll(); // Послать уведомление всем ожидающим потокам
}

Слайд 24

Взаимодействие потоков на примере

Задача. Есть производитель и потребитель данных - целых чисел.

Взаимодействие потоков на примере Задача. Есть производитель и потребитель данных - целых
Имеется склад данных, способный хранить одно число, принимать и выдавать число со склада. Необходимо так организовать работу системы, чтобы склад не переполнялся и чтобы потребитель не получал одно и то же число дважды
Программа состоит из 3-х классов:
Producer - поток-производитель данных;
Consumer - поток-потребитель данных;
Store - склад
class Producer implements Runnable {
Store store; // Ссылка на объект-склад
Producer(Store store) { // Конструктор запускает поток-производитель
this.store = store;
(new Thread(this)).start();
}
public void run() {
for (int i = 0; ; i++)
store.put(i); // Производитель отсылает числа а склад
}
}

Слайд 25

Взаимодействие потоков на примере

class Consumer implements Runnable {
Store store; // Ссылка на

Взаимодействие потоков на примере class Consumer implements Runnable { Store store; //
объект-склад
// Конструктор запускает поток-потребитель
Consumer(Store store) {
this.store = store;
(new Thread(this)).start();
}
// Потребитель получает числа со склада
public void run() {
for (;;)
store.get();
}
}

Слайд 26

Взаимодействие потоков на примере

public class Store {
private int n; // Хранимое на

Взаимодействие потоков на примере public class Store { private int n; //
складе число
private boolean hasData; // Признак наличия данных на складе
synchronized int get() {
while (!hasData) { // Если склад пуст, поток пришедший за данными, должен ждать
try {
wait();
} catch (InterruptedException ex) {}
}
// Если число есть на складе, оно выдается
hasData = false;
notifyAll();
System.out.println("Выдано со склада " + n);
return n;
}
synchronized void put(int n) {
while (hasData) { // Если склад полон, поток принесший данные, должен ждать
try {
wait();
} catch (InterruptedException ex) {}
}
// Если склад пуст, число принимается на склад
this.n = n;
hasData = true;
notifyAll();
System.out.println("Поступило на склад " + n);
}
}

Слайд 27

Взаимодействие потоков на примере

В методе main() главный поток запускает два дочерних и

Взаимодействие потоков на примере В методе main() главный поток запускает два дочерних
тут же прекращается:
public static void main(String[] args) {
Store store = new Store();
Producer p = new Producer(store);
Consumer c = new Consumer(store);
}
В результате получим следующий вывод:
Поступило на склад 0
Выдано со склада 0
Поступило на склад 1
Выдано со склада 1
Поступило на склад 2
Выдано со склада 2
...
Нетрудно убедиться, что если не вызывать методы wait() и notifyAll(), работа программы становится хаотической

Слайд 28

Взаимная блокировка потоков (deadlock)

В простейшем случае взаимная блокировка возникает, когда два потока

Взаимная блокировка потоков (deadlock) В простейшем случае взаимная блокировка возникает, когда два
используют два синхронизированных объекта
Пусть первый поток вводит монитор в объект A, а второй поток вводит монитор в объект B.
После этого первый поток пытается ввести монитор в объект В, но переходит в ожидание, т.к. объект В занят вторым потоком. Второй поток не выполняется, он ждет освобождения объекта A.
Получается, что оба потока не могут продолжать выполнение – возникает ситуация, которую называют DeadLock

Слайд 29

Пример взаимной блокировки потоков

public class DeadLocker extends Thread{
Object a, b;
public DeadLocker (Object

Пример взаимной блокировки потоков public class DeadLocker extends Thread{ Object a, b;
a, Object b, String name) {
super(name);
this.a = a;
this.b = b;
}
public void run() {
// ввод монитора а
synchronized (a) {
System.out.println(getName() + ": step A");
// передача процессора другому потоку
sleep(0);
// ввод монитора b
synchronized (b) {
System.out.println(getName() + ": step B");
};
};
}
}

Слайд 30

Пример взаимной блокировки потоков

Для демонстрации взаимной влокировки используем следующий код:
// Объекты a

Пример взаимной блокировки потоков Для демонстрации взаимной влокировки используем следующий код: //
и b, которые послужат мониторами
Object a = new Object(),
Object b = new Object();
// Первый поток получает объекты a и b
new DeadLocker(a, b, "First").start();
// Второй поток получает те же объекты в другом порядке
new DeadLocker (b, a, "Second").start();
Этот код напечатает:
First: step A
Second: step A
после чего потоки заблокируются
Если вызов метода sleep(0) заменить на вызов метода a.wait(), взаимной блокировки не произойдет !!!

Слайд 31

Завершение потока

Поток продолжает действовать (метод потока isAlive() возвращает true), пока не произойдет

Завершение потока Поток продолжает действовать (метод потока isAlive() возвращает true), пока не
одно из 3-х событий
метод run() естественно завершится
работа метода run() будет прервана
будет вызван метод потока destroy()
Вызов метода destroy() немедленно разрушает поток.
Все блокировки, сделанные потоком остаются, поэтому другие потоки могут легко попасть в состояние бесконечного ожидания.
Многими операционными системами метод destroy() не поддерживается, и его применение вызывает исключение java.lang.NoSuchMethodError в вызывающем потоке.
Применять для завершения потока метод destroy() нельзя !!!

Слайд 32

Пример корректного завершения потока

Корректного прекращения работы потока можно добиться, поместив в цикл

Пример корректного завершения потока Корректного прекращения работы потока можно добиться, поместив в
метода run() проверку флага завершения:
public class MyThread extends Thread {
// Флаг завершения и метод для его установки
private boolean stopFlag = false;
public void stopIt() {
stopFlag = true;
}
// Поток, который печатает свое имя каждые полсекунды
public void run() {
while (!stopFlag) {
System.out.println(getName());
try {
sleep(500);
}
catch (InterruptedException ex) {}
}
}
}
Такой подход не учитывает того, что в момент желаемой остановки поток MyThread может выполнять длительный метод sleep() или wait()

Слайд 33

Прерывание потоков

Механизм прерывания в классе Thread поддерживается методами:
interrupt() – устанавливает состояние потока

Прерывание потоков Механизм прерывания в классе Thread поддерживается методами: interrupt() – устанавливает
в значение “прерван”
isInterrupted() – проверяет, прерван ли поток
interrupted() – то же самое, но еще и сбрасывает его
Замечание. В Java 1 для приостановки, перезапуска и остановки потока использовали методы suspend(), resume() и stop().
Эти методы не снимают блокировки, установленные потоком, и поэтому неприменимы !!!

Слайд 34

Пример завершения потока с interrupt()

public class MyThread extends Thread{
public void run() {
while

Пример завершения потока с interrupt() public class MyThread extends Thread{ public void
(!interrupted()) {
System.out.println(getName());
try {
sleep(500);
}
catch (InterruptedException ex) {
interrupt();
}
}
}
}
Повторный вызов метода interrupt() в блоке catch необходим потому, что метод sleep(), выбросив исключение, так же очищает состояние потока "прерван", как и метод interrupted()

Слайд 35

Ожидание завершения потока

Если главный поток решил дожидаться завершения работы дочернего потока, он

Ожидание завершения потока Если главный поток решил дожидаться завершения работы дочернего потока,
может сделать это, организовав цикл с проверкой условия isAlive() для объекта дочернего потока.
Чтобы ожидание не было активным, в цикле можно выполнять метод sleep()
public static void main(String[] args) throws Exception{
MyThread thread = new MyThread();
thread.start();
while (thread.isAlive())
Thread.sleep(100);
System.out.println("Конец");
}
Другой вариант приостановить один поток до завершения другого – с использованием метода join()
Имя файла: Базовые-свойства-многопоточности.pptx
Количество просмотров: 52
Количество скачиваний: 0