06 Взаимодействие потоков. Рефлексия.ppt
- Количество слайдов: 18
Java Express 6. Взаимодействие потоков. Рефлексия
2 Проблема взаимного исключения n n Другие названия: "Проблема критической секции", "Состязания потоков", "Гонки" Когда возникает проблема: в случае, если: ¨ ¨ n n два потока одновременно обращаются к одному и тому же ресурсу; при этом хотя бы один из потоков изменяет состояние ресурса. Под ресурсом здесь может пониматься переменная, файл, устройство и любой другой объект, который может изменяться. Суть проблемы: если поток A пытается прочесть состояние сложного объекта в то время, как поток B изменяет это состояние, то A может получить "неполностью измененное", некорректное значение; ¨ если оба потока A и B пытаются одновременно изменить состояние объекта, то результат может оказаться либо некорректным, либо просто неверным. ¨ n n В то же время, последовательное обращение A и B к тому же объекту не создает проблем. Поскольку одновременное обращение потоков к одному объекту – вещь случайная, проблема может долгое время не проявляться. Это затрудняет отладку приложения и требует тщательного анализа в ходе проектирования многопоточной программы.
3 Пример состязания потоков Постановка задачи. Объект Store содержит поле count и метод incr. Count. Два потока вызывают этот метод по 50 раз каждый. n class Store { static int count = 0; static void incr. Count() { count++; } } class My. Thread extends Thread { public void run() { for (int i = 0; i < 50; i++) Store. incr. Count(); } } n При правильной работе каждый вызов из метода должен увеличивать значение count на 1.
4 Продолжение примера public class Main { public static void main(String[] args) throws Interrupted. Exception { Thread th 1 = new My. Thread(); Thread th 2 = new My. Thread(); th 1. start(); th 2. start(); th 1. join(); th 2. join(); System. out. println("main: count = " + Store. count); } } n n Основной поток запускает потоки th 1 и th 2, каждый из которых 50 раз увеличивает величину Store. count. Метод join заставляет основной поток ждать завершения th 1 и th 2. При этом ожидании возможны (в принципе) прерывания, поэтому нужен перехват исключений. Ожидаемый результат работы программы: count == 100. На самом деле при многократных запусках программы чаще всего выдается ответ 100, но бывает и 99, и 98, даже 92 ? ? ?
5 Объяснение примера n Причина неоднозначности ответа в том, что операция ++ в Java не является неделимой, т. е. выполняется примерно так: tmp = count; tmp++; count = tmp; Пусть в какой-то момент времени count == 10. Есть вероятность, что посреди выполнения метода одним потоком вмешается другой поток: n th 1: tmp 1 = count; tmp 1++; count = tmp 1; n n // 10 // 11 th 2: tmp 2 = count; tmp 2++; count = tmp 2; // 10 // 11 В результате вместо правильного значения 12 поле count получит значение 11. Ошибка полностью исчезает, если исправить заголовок метода incr. Count: static synchronized void incr. Count()
6 Блокировка потоков n n n Если некоторые методы одного класса объявлены с модификатором synchronized, то только один поток может выполнять какой-то из этих методов. Например, если в классе определены синхронизированные методы method 1 и method 2, и поток th 1 в настоящее время выполняет метод method 1, то при попытке потока th 2 вызвать любой из методов method 1 или method 2 этот поток будет заблокирован (временно приостановлен). Когда поток th 1 выйдет из метода method 1, система разблокирует поток th 2 и позволит ему выполнять синхронизированный метод. Блокировка работает для статических методов класса либо для методов одного и того же объекта – экземпляра класса. Синхронизированные методы двух разных объектов могут выполняться одновременно разными потоками. Можно объявить синхронизированным не вызываемый метод, а некоторый блок программы вызывающего потока. При этом нужно указать объект блокировки. synchronized (my. Blocking. Object) {. . . } n Не могут одновременно выполняться разными потоками блоки, синхронизированные по одному и тому же объекту.
7 Ожидание потока n Есть возможность организовать ожидание одним потоком некоторых условий, обеспечиваемых другим потоком. ¨ Важно, что при этом удается избежать циклического опроса: "Выполнено ли уже условие? " (активного ожидания). Поток переводится в состояние ожидания, в котором он не расходует процессорное время (пассивное ожидание). n Простейшая форма: ожидание вызывающим потоком момента завершения вызываемого потока (метод join был описан раньше). n Ожидание одним потоком извещения от другого потока: final void wait() – метод класса Object, может вызываться только тем потоком, который в данный момент выполняет синхронизированный по данному объекту метод или блок. Вызывающий поток временно снимает блокировку с объекта и засыпает; ¨ final void wait(long timeout) – то же самое, но время сна ограничено. ¨ final void notify() – метод класса Object, может вызываться только тем потоком, который в данный момент выполняет синхронизированный по данному объекту метод или блок. Метод пробуждает один из потоков, которые вызвали wait по данному объекту. Проснувшийся поток пытается захватить блокировку и продолжить свое выполнение. ¨ final void notify. All() – пробуждаются все потоки, вызвавшие wait по данному объекту. ¨
8 Пример: задача "Поставщик / Потребитель" n Постановка задачи. Поток Producer производит изделия (например, пирожки, телевизоры, танки, …) и помещает их на склад под возрастающими номерами. Поток Consumer берет со склада изделие с наименьшим номером. Если на складе пусто, Consumer ждет. class Store { private int first = 1, last = 1; synchronized void put. Item() throws Interrupted. Exception { Thread. sleep(10); System. out. println("put: " + last++); notify(); } synchronized void get. Item() throws Interrupted. Exception { while (last == first) { wait(); } System. out. println("get: " + first++); } n n } Здесь нельзя обойтись статическими переменными и методами, поскольку методы notify и wait нестатические. Вызов wait окружен циклом на случай, если спящих потребителей несколько. Неизвестно, который из них успеет съесть пирожок (возможно, этот цикл не нужен, но кому он мешает? ).
9 Продолжение примера class Producer extends Thread { Store st; Producer(Store st) {this. st = st; } public void run() { for (int i = 0; i < 100; i++) try { st. put. Item(); } catch (Interrupted. Exception ex) { } } } class Consumer extends Thread { Store st; Consumer(Store st) {this. st = st; } public void run() { while (true) { try { st. get. Item(); } catch (Interrupted. Exception ex) { break; } }
10 Окончание примера public class Main { public static void main(String[] args) throws Interrupted. Exception { Store st = new Store(); Consumer cons. Thread = new Consumer(st); Producer prod. Thread = new Producer(st); cons. Thread. start(); prod. Thread. join(); cons. Thread. interrupt(); } } n n Создаются экземпляр Store и два потока, работающие с ним. Основной поток дожидается завершения поставщика и потом намекает потребителю, что пора заканчивать. put: 1 Пример выдачи на консоль: put: 2 put: 3 get: 1 get: 2 get: 3 put: 4 put: 5 get: 4 get: 5. . .
11 Проблема взаимной блокировки потоков n n n Другие названия: "проблема тупиков", "клинч", "deadlock". Когда возникает проблема: в случае, если группа из двух или более потоков оказывается в состоянии блокировки или не ограниченного по времени ожидания, причем пробуждение каждого потока зависит от другого потока из той же группы. Причиной ожидания может быть вызов wait, join или синхронизированного метода/блока. Суть проблемы: блокировка оказывается вечной, программа зависает. Простейший пример: th 1: th 2. join(); n n th 2: th 1. join(); В более сложных случаях возникновение тупика может зависеть от случайных обстоятельств (например, от времени выполнения различных методов), поэтому обнаружить угрозу тупика при отладке программы непросто. Но надо. Одной из мер профилактики тупиков может служить ограничение времени ожидания по wait и join, с выдачей ошибки, если ожидание явно затянулось.
12 Рефлексия n n Понятие "рефлексия" в Java означает средства языка, позволяющие в ходе работы программы получать и использовать информацию о классах, объектах и интерфейсах, используемых в программе. Средства рефлексии, в частности, позволяют: ¨ ¨ ¨ n n n определить класс объекта; получить информацию о полях, методах и конструкторах класса; создать экземпляр класса, имя которого становится известно только на этапе выполнения программы; прочитать или изменить значение поля, имя которого становится известно на этапе выполнения; вызвать таким же образом метод объекта. В обычных программах средства рефлексии не нужны. В большинстве языков программирования таких средств нет, и живут же люди. Принцип полиморфизма как раз позволяет автоматически использовать необходимый минимум знаний о классах. Везде, где можно обойтись без рефлексии, надо обходиться без этих мощных, но небезопасных и замедляющих выполнение средств. Тем не менее, рефлексия необходима при разработке системных программ (отладчиков, браузеров кода) и для работы с компонентами Java. Beans, которые будут рассмотрены в дальнейшем.
13 Класс Class n n Объекты системного класса Class представляют собой описания конкретных классов. Для каждого класса, объекты которого используются в программе, создается объект типа Class. Такой объект создается также для примитивных типов, массивов и конструкторов. Некоторые методы: ¨ ¨ ¨ ¨ ¨ Class get. Class() – возвращает объект типа Class для данного объекта любого ссылочного типа; не работает для примитивных типов; static Class for. Name(String class. Name) – возвращает объект типа Class по заданному имени класса; String get. Name() – возвращает имя данного класса; public int get. Modifiers() – возвращет модификаторы класса, закодированные целым числом; Field[] get. Fields() – возвращает массив public полей класса; Method[] get. Methods() – возвращает массив public методов; Constructor[] get. Constructors() – возвращает массив public конструкторов; boolean is. Array() – истина, если объект Class представляет массив; boolean is. Instance(Object obj) – истина, если obj может быть присвоен переменной данного класса; T new. Instance() – создает экземпляр класса, представленного данным объектом Class, аналогично вызову конструктора без параметров.
14 Класс Field n n Объекты системного класса Field представляют собой описания полей объекта. Некоторые методы: ¨ ¨ ¨ ¨ ¨ Class get. Type() – возвращает тип данного поля; String get. Name() – возвращает имя данного поля; Object get(Object obj) – возвращает текущее значение данного поля для указанного объекта; примитивные типы преобразуются в типы-оболочки; int get. Int(Object obj) – возвращает текущее значение данного поля для указанного объекта как int (если это возможно); boolean get. Boolean(Object obj) – возвращает текущее значение данного поля как boolean (если это возможно); и т. п. для полей других примитивных типов; void set(Object obj, Object value) – присваивает значение данного поля у указанного объекта; void set. Boolean(Object obj, boolean z) – присваивает булевское значение данного поля у указанного объекта; и т. п. для полей других примитивных типов; String to. String() – возвращает строку описания данного поля.
15 Класс Method n n Объекты системного класса Method представляют собой описания методов объекта. Некоторые методы: ¨ ¨ ¨ Class get. Declaring. Class() – возвращает класс, в котором описан данный метод; String get. Name() – возвращает имя данного метода; Class[] get. Parameter. Types() – возвращает массив типов всех параметров метода; Class > get. Return. Type() – возвращает тип возвращаемого значения метода; Object invoke(Object obj, Object. . . args) – вызывает данный метод у указанного объекта и с перечисленными параметрами, возвращает значение, возвращаемое методом; String to. String() – возвращает строку описания данного метода.
16 Пример import java. lang. reflect. *; public class Main { public static void main(String[] args) throws Class. Not. Found. Exception { String my. Str = "Hello!"; Class cl 1 = my. Str. get. Class(); System. out. println(cl 1. get. Name()); Field flds 1[] = cl 1. get. Fields(); for (int i = 0; i < flds 1. length; i++) { Field field = flds 1[i]; System. out. println(field. to. String()); } System. out. println(); Integer my. Int = 100; Class cl 2 = my. Int. get. Class(); System. out. println(cl 2. get. Name()); Field flds 2[] = cl 2. get. Fields(); for (int i = 0; i < flds 2. length; i++) { Field field = flds 2[i]; System. out. println(field. to. String()); } } }
17 Результат работы примера java. lang. String public static final java. util. Comparator java. lang. String. CASE_INSENSITIVE_ORDER java. lang. Integer public static final int java. lang. Integer. MIN_VALUE int java. lang. Integer. MAX_VALUE java. lang. Class java. lang. Integer. TYPE int java. lang. Integer. SIZE
18 Задание 1. 2. 3. Доработать пример "Поставщик / Потребитель" при условии, что имеются два конкурирующих потребителя, а размер склада (last first) ограничен, и при заполнении склада поставщик вынужден ждать. Знаменитая "Задача о китайских философах". Пять бедных философов сидят вокруглого стола. Между ними лежат пять палочек для еды. Каждый философ случайное время размышляет, потом берет палочки справа и слева от себя и случайное время ест. Потом снова размышляет и т. п. Если хотя бы из палочек занята, философ ждет, пока она освободится, удерживая другую палочку. Выдать на консоль протокол мероприятия: "Философ 2 размышляет", "Философ 4 ждет палочку", "Философ 4 начинает есть" и т. п. Приведет ли такая модель к тупику и как его избежать? Описать несложный класс (например, класс Point с полями x, y, методами move и print, одним конструктором). Создать объект данного класса. Используя средства рефлексии, распечатать все, что удастся узнать об этом объекте, создать через рефлексию другой объект того же класса, вызвать его методы.


