6 Threads.pptx
- Количество слайдов: 44
Многопоточное программирование Разработка приложений на языке Java
Вопросы Потоки в Java ◦ Понятие потока. Состояния потока ◦ Класс Thread и интерфейс Runnable Синхронизация потоков Взаимодействие потоков ◦ Ожидание и оповещение ◦ Временная упорядоченность потоков ◦ Завершение выполнения потоков 2
I. Потоки в Java 3
Понятие потока Поток (в широком смысле) – процедура, выполняемая параллельно с основной программой и другими потоками В Java поток описывается классом java. lang. Thread, который инкапсулирует программный код, выполняемый параллельно с кодом других потоков и отвечает за взаимодействие с диспетчером ОС 4
Жизненный цикл потока 5
Создание и запуск потока Для порождения нового потока необходимо создать объект Thread. Функциональность потока может быть реализована с помощью подкласса Thread, переопределяющего метод run: ◦ public void run() Основные конструкторы Thread: ◦ Thread() Конструктор по умолчанию, присваивает потоку автоматическое имя вида “Thread-XXX” ◦ Thread(String name) Конструктор, явно задающий имя потока Для запуска потока используется метод start() ◦ public void start() ◦ Запускает поток и переводит его в состояние готовности ◦ Поток можно запустить не более одного раза 6
Пример class Printer extends Thread { private String s; private int count; public Printer(String s, int c) { super("Printer"); this. s = s; this. count = c; } class Main { public static void main(String[] args) { new Printer("Hello, ", 50). start(); new Printer("World", 50). start(); } } @Override public void run() { for (int i = 0; i < count; i++) { System. out. println(s); } } } 7
Создание и запуск потока Функциональность потока может быть инкапсулирована в классе, реализующем интерфейс Runnable (пакет java. lang) public interface Runnable { public void run(); } Во многих случаях реализация Runnable выполняется в виде анонимного класса Экземпляр Runnable передается в один из конструкторов Thread: ◦ Thread(Runnable target) ◦ Thread(Runnable target, String name) Использование Runnable является предпочтительным способом описания функциональности потока с точки зрения дизайна Если потомок класса Thread переопределяет метод run(), его контракт с Runnable, вообще говоря, не действует 8
Пример class Printer implements Runnable{ private String s; private int count; public Printer(String s, int c) { this. s = s; this. count = c; } @Override public void run() { for (int i = 0; i < count; i++) { System. out. println(s); } } class Main { public static void main(String[] args) { new Thread( new Printer("Hello, ", 50) ). start(); new Thread( new Printer("World", 50) ). start(); } } } 9
Основные свойства потока доступны через соответствующий объект Thread Признак "живого" потока ◦ public boolean is. Alive() ◦ возвращает true, если поток запущен и еще не завершился Имя потока ◦ public String get. Name() ◦ public void set. Name(String name) 10
Основные свойства потока Состояние потока ◦ public State get. State() ◦ возвращает состояние потока в виде объекта перечисления Thread. State Варианты Thread. State ◦ ◦ ◦ NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED 11
Основные свойства потока Признак потока-демона Приоритет ◦ public boolean is. Daemon() ◦ public void set. Daemon(boolean value) ◦ Поток-демон не препятствует автоматическому завершению работы виртуальной машины ◦ Установка статуса запрещена после запуска потока ◦ public int get. Priority() ◦ public void set. Priority(int priority) ◦ Стандартные значения: Thread. MIN_PRIORITY, Thread. NORM_PRIORITY, Thread. MAX_PRIORITY ◦ Смысл приоритета определяется реализацией диспетчера потоков JVM и операционной системы Получить ссылку на текущий поток можно с помощью метода current. Thread(): ◦ public static Thread current. Thread() 12
II. Синхронизация потоков 13
Мониторы и блокировки Синхронизация - простейший механизм взаимодействия потоков в Java, основанный на использовании мониторов С каждым Java-объектом ассоциирован монитор, который может блокироваться потоком После того, как один поток блокировал монитор некоторого объекта, другие потоки при попытке заблокировать этот же монитор, перейдут в состояние BLOCKED и будут находиться в нем, пока первый поток не освободит монитор Выбор первого потока из числа конкурирующих за блокировку монитора определяется реализацией виртуальной машины и ОС 14
Синхронизация В языке Java операции с мониторами поддерживаются с помощью синхронизированных операторов и методов Синхронизированный оператор: synchronized (Выражение) { Оператор; … Оператор; } Порядок выполнения потоком t: 1. 2. 3. 4. Вычисляется Выражение. Пусть результат – объект x Поток t блокирует монитор x Выполняется тело синхронизированного оператора Поток t снимает блокировку x 15
Синхронизированные методы Для упрощения синхронизации программного кода целого метода используется модификатор synchronized Эффект от synchronized – обертка тела метода в неявный синхронизированный оператор. Объект синхронизации зависит от вида метода: ◦ Если метод статический, объектом синхронизации будет экземпляр Class, соответствующий классу, в котором описан метод ◦ Если метод не статический, то объектом синхронизации будет текущий объект, связанный со ссылкой this 16
Синхронизированные методы class A { public synchronized void do. X() { /* some code here */ } public static synchronized void do. Y() { /* some code here */ } } class A { public void do. X() { synchronized(this) {/* some code here */} } public static void do. Y() { synchronized(A. class) {/* some code here */} } } 17
Пример синхронизации class Account { private int balance; public synchronized int get. Balance() { return balance; } public synchronized boolean transfer(int amount) { if (amount == 0) return true; if (amount > 0) { balance += deposit; return true; } int new. Balance = balance + amount; if (new. Balance < 0) return false; balance = new. Balance; return true; } } 18
Синхронизация Некорректное использование синхронизации на нескольких объектах может привести к взаимоблокировке (deadlock). Обнаружение или предотвращение взаимоблокировок компилятором или виртуальной машиной не специфицируется и должно обеспечиваться разработчиком. Некоторые полезные и безопасные варианты синхронизации реализованы в библиотеке Java Concurrency (пакет java. util. concurrent) 19
Распределение памяти Каждый поток имеет собственный стек и рабочую память. Все потоки, кроме того, имеют доступ к основной динамической памяти (куча). Обмен данными между потоками происходит только через основную память Локальные переменные и параметры методов хранятся в стеке и никогда не разделяются несколькими потоками Все остальные переменные (поля объектов и классов, а также элементы массивов) хранятся в основной памяти, а также в рабочей памяти использующих их потоков 20
Синхронизация памяти Поток, использующий переменные из кучи, должен взаимодействовать с основной памятью В отсутствие синхронизации компилятор и виртуальная машина могут оптимизировать доступ к разделяемым переменным. В частности, изменения общих переменных одним потоком могут быть в определенные моменты времени невидимы для других потоков, а разные потоки могут прочитать разные значения одной и той же переменной 21
Синхронизация памяти Если же поток выполняет блокировку какоголибо монитора, то перед чтением любой переменной из рабочей памяти он должен загрузить ее значение из основной памяти Все переменные рабочей области, измененные потоком, должны быть записаны в основную память до снятия блокировки Таким образом, переменные, используемые внутри synchronized-блока, всегда синхронизированы с основной памятью 22
Пример class Sample { int a = 1, b = 2; void left() {a = b; } void right() {b = a; } } Sample x = new Sample(); Поток 1 вызывает x. left() Поток 2 вызывает x. right() Что будет в основной памяти, когда они завершатся? a=2, b=2 a=1, b=1 Синхронизированные варианты a=2, b=2 a=1, b=1 a=2, b=1 23
Синхронизированные коллекции Для безопасного использования модифицируемых коллекций в стандартной библиотеке Java существуют специальные классы, которые оборачивают существующие коллекции и синхронизируют их методы Методы для создания синхронизированных коллекций собраны в классе Collections: public static <T> Collection<T> synchronized. Collection(Collection<T> c) public static <T> Set<T> synchronized. Set(Set<T> s) public static <T> Sorted. Set<T> synchronized. Sorted. Set(Sorted. Set<T> s) public static <T> List<T> synchronized. List(List<T> list) public static <K, V> Map<K, V> synchronized. Map(Map<K, V> m) public static <K, V> Sorted. Map<K, V> synchronized. Sorted. Map(Sorted. Map<K, V> m) Объектом синхронизации является коллекция, возвращаемая методом synchronized. XXX() Синхронизируются только отдельные методы. При необходимости итераторы и более сложные алгоритмы над коллекциями должны синхронизироваться разработчиком 24
III. Взаимодействие потоков 25
Сон Поток может ввести себя в состояние сна, вызвав метод sleep() класса Thread ◦ public static void sleep(long ms) ◦ Параметр метода определяет интервал времени (в миллисекундах), в течение которого поток должен находиться в состоянии сна. ◦ Метод sleep может выбрасывать проверяемое исключение Interrupted. Exception, которое возникает в том случае, если другой поток выполняет прерывание данного потока во время его сна. ◦ Фактическое время между вызовом метода sleep() и возобновлением работы потока может быть больше заявленного интервала. Исключение составляет ситуация прерывания. ◦ После "пробуждения" поток переходит в состояние готовности ◦ В состоянии сна поток сохраняет все блокировки 26
Yield Метод yield() класса Thread используется для переноса вызывающего потока из состояния выполнения в состояние готовности ◦ public static void yield() Эффект метода yield() зависит от реализации диспетчера потоков в виртуальной машине и ОС ◦ В системах без расщепления времени вызов yield() позволяет уменьшить риск ситуации "голодания" (starvation) для потоков с одинаковым приоритетом ◦ В системах с расщеплением времени вызов yield() может игнорироваться 27
Ожидание завершения Если для продолжения работы некоторому потоку требуется результат работы процесса, выполняющегося в другом потоке, он должен дождаться его завершения Класс Thread позволяет реализовать ожидание завершения с помощью метода join: ◦ public void join() Ожидание завершения потока ◦ public void join(long ms) Ожидание завершения потока или истечения заданного интервала в миллисекундах (в зависимости от того, какое событие произойдет раньше). ◦ Оба варианта метода join могут генерировать исключение Interrupted. Exception в случае прерывания ожидающего потока (при этом метод join() может вернуть управление раньше завершения целевого потока) 28
Ожидание завершения static class Worker implements Runnable { private Object result; public Object get. Result() {return result; } public void run() {. . . result =. . . ; } } public static void main(String[] args) { Worker worker = new Worker(); Thread worker. Thread = new Thread(worker); worker. Thread. start(); . . . try { worker. Thread. join(); } catch (Interrupted. Exception e) {} Object result = worker. get. Result(); . . . System. out. println(result); } 29
Ожидание и оповещение Ожидание завершения потока является частным случаем более общего механизма взаимодействия потоков в Java - механизма ожидания и оповещения Данный механизм предполагает использование сигнального объекта, с помощью которого потоки координируют свои действия В качестве сигнального объекта может выступать любой Java-объект. Соответствующая функциональность представлена методами класса Object: wait(), notify() и notify. All() 30
Ожидание С каждым Java-объектом ассоциировано множество ожидающих потоков. Сразу после создания объекта оно пусто Метод wait() помещает вызывающий поток в "зал ожидания" объекта: ◦ public void wait() ◦ public void wait(long ms) ◦ В момент вызова метода wait() у сигнального объекта, поток должен обладать блокировкой его монитора При переходе в состояние ожидания поток снимает все блокировки сигнального объекта 31
Ожидание Поток находится в состоянии ожидания до тех пор, пока не произойдет одно из следующих событий ◦ Сигнальный объект получит оповещение ◦ Пройдет интервал, указанный при вызове метода wait() (для второго варианта wait()) ◦ Произойдет прерывание ожидающего потока При выходе из состояния ожидания поток вначале заново получает все ранее снятые блокировки сигнального объекта В случае прерывания метод wait() генерирует исключение Interrupted. Exception 32
Оповещение – это сообщение потоку о потенциальной возможности выйти из состояния ожидания. Для выполнения оповещения сторонний поток должен вызвать у сигнального объекта один из следующих методов: ◦ public void notify() Удаляет из "зала ожидания" один (произвольно выбранный) поток и возвращает его в состояние готовности ◦ public void notify. All() Удаляет из "зала ожидания" все потоки и возвращает их в состояние ожидания Поток, вызывающий методы notify()/notify. All(), должен предварительно получить блокировку монитора сигнального объекта Освобожденные потоки будут фактически находиться в состоянии блокировки, пока поток, выполнивший оповещение, не разблокирует монитор сигнального объекта 33
Ожидание и оповещение Основной шаблон ожидания: synchronized(сигнальный. Объект) { … while (Условие. Не. Выполнено) { try { сигнальный. Объект. wait(); } catch (Interrupted. Exception e) { Реакция на прерывание } } … } Основной шаблон оповещения: synchronized(сигнальный. Объект) { … Изменить. Условие. Ожидания; сигнальный. Объект. notify. All(); … } 34
Поставщик-потребитель public class Data { private int value; private boolean available = false; public synchronized int get() { while (!available) { try { wait(); } catch (Interrupted. Exception e) { } } available = false; notify. All(); return contents; } } public synchronized void put(int value) { while (available) { try { wait(); } catch (Interrupted. Exception e) { } } available = true; this. value = value; notify. All(); } 35
Поставщик-потребитель public class Producer implements Runnable { private Data data; private int count; public Producer(Data data, int count) { this. data = data; this. count = count; } } public void run() { for (int i = 0; i < count; i++) { int value = (int)(Math. random() * 100); data. put(value); System. out. println( Thread. current. Thread(). get. Name() + ": " + value ); try { sleep((int)(Math. random() * 100)); } catch (Interrupted. Exception e) { // Nothing to do } } } 36
Поставщик-потребитель public class Consumer implements Runnable { private Data data; private int count; public Consumer(Data data, int count) { this. data = data; this. count = count; } } public void run() { for (int i = 0; i < count; i++) { value = data. get(); System. out. println( Thread. current. Thread(). get. Name() + ": " + value ); try { sleep((int)(Math. random() * 100)); } catch (Interrupted. Exception e) { // Nothing to do } } } 37
Управление потоком Первоначально для управления потоком предназначались методы suspend(), resume() и stop() класса Thread В последних версиях Java эти методы рассматриваются как устаревшие, поскольку их использование может приводить к некорректной работе приложения. Новые приложения должны реализовать собственный механизм управления потоком, либо использовать Future-объекты из библиотеки Java Concurrency 38
Прерывания Прерывание – это стандартный механизм оповещения потока о намерении завершить его работу. Использование прерываний основано на трех методах класса Thread: ◦ public void interrupt() Устанавливает признак прерывания в true и выполняет прерывание. Обычно вызывается сторонним потоком ◦ public boolean is. Interrupted() Возвращает значение признака прерывания ◦ public static boolean interrupted() Возвращает значение признака прерывания для потока, вызвавшего метод, и сбрасывает признак в false Использование прерываний не дает гарантии завершения потока. Для их корректной реализации необходима кооперация целевого потока и потока -контроллера 39
Прерывания Если при выполнении прерывания целевой поток находился в состоянии ожидания, сна, он возвращается в состояние готовности, а метод, вызвавший приостановку (wait, join, sleep), выбрасывает исключение Interrupted. Exception. После того, как поток станет активным, он сможет обработать это исключение, проверить состояние признака прерывания и принять решение о том, следует ли завершать работу или нет 40
Поставщик-потребитель public class private Consumer implements Runnable { Data data; int count; Control conrol; public Consumer(Data data, int count, Control control) { this. data = data; this. count = count; this. control = control; } } public void run() { for (int i = 0; i < count; i++) { if (!control. sync()) { return; } value = data. get(); System. out. println( Thread. current. Thread(). get. Name() + ": " + value ); try { sleep((int)(Math. random() * 100)); } catch (Interrupted. Exception e) { return; } } } 41
Поставщик-потребитель public class Control { private boolean running = true; private boolean stopped = false; public synchronized boolean is. Running() {return running; } public synchronized boolean is. Stopped() {return stopped; } public synchronized void stop() {stopped = true; } public synchronized void set. Running(boolean value) { if (running != value) { running = value; if (running) { notify. All(); } } public synchronized boolean sync() { if (is. Stopped()) { Thread. current. Thread(). interrupt(); return false; } while (!is. Running()) { try { wait(); } catch (Interrupted. Exception e) { return false; } } return true; } 42
Поставщик-потребитель public class Controller { private final Control control = new Control(); public Consumer() {} public Control get. Control() {return control; } public void suspend() { control. set. Running(false); } public void resume() { control. set. Running(true); } } public void stop() { control. stop(); } 43
Ссылки и литература D. Lea. Concurrent Programming in Java™: Design Principles and Patterns, 1999 Arnold K. , Gosling J. , Holmes D. Java Programming Language, 2005 J. Bloch. Effective Java, 2008 K. A. Mughal, R. W. Rasmussen. A Programmer's Guide To Java SCJP Certification. A Comprehensive Primer, 2008 Библиотека Java Concurrency http: //java. sun. com/j 2 se/1. 5. 0/docs/guide/c oncurrency/ 44
6 Threads.pptx