Лекция 6 Взаимоблокировка потоков Если имеются два потока и два объекта, подлежащих блокированию, возникает опасность возникновения взаимоблокировки каждый.

Презентация:



Advertisements
Похожие презентации
МНОГОПОТОЧНОЕ ПРОГРАММИРОВАНИЕ В JAVA Пакеты java.lang java.util.concurrent.
Advertisements

Многопоточное программирование на Java Java Advanced.
Многопоточное программирование на Java Java Advanced.
ПОТОКИ Начальные сведенияПОТОКИ Начальные сведения.
1 Обработка исключений в Java Одно из важнейших преимуществ Java – разработанный на уровне языка механизм обработки исключений. Исключение в Java - это.
b5_java_s4
Параллелизм и потоки в Java For students of university Author: Oxana Dudnik.
Test 6 Вопрос 1. Как можно уничтожить объект в Java? a)присвоить null всем ссылкам на объект b)вызвать Runtime.getRuntime().gc() c)вызвать метод finalize()
Практическое занятие 6. Функции. Большинство языков программирования используют понятия функции и процедуры. C++ формально не поддерживает понятие процедуры,
Перегрузка операторов x = a + b результат 1-й операнд2-й операнд оператор По количеству операндов операторы делятся на: унарные (один операнд) бинарные.
Основы информатики Классы Заикин Олег Сергеевич zaikin.all24.org
Синтаксис языка Java. Символы и синтаксис Перевод строчки эквивалентен пробелу Регистр в именах различается.
Наследование Наследование – это отношение является между классами. class Person { string first_name; int birth_year;... } class Student : Person { float.
Saint Petersburg, 2011 Java Lecture #06 Exceptions.
Параллельное программирование с использованием технологии OpenMP Аксёнов Сергей Владимирович к.т.н., доцент каф.ОСУ ТПУ Лекция 3 Томский политехнический.
События События Важная роль делегатов заключается в том, что на них основана модель событий С#. Применение событий вовсе не ограничено приложениями с графическим.
Модель приложений.NET. Среда платформы Win32, в которой выполняется программа, называется ее процессом. Эта среда состоит из: адресного пространства,
1 Java 14. Параллельное выполнение. 2 Терминология При переводе на русский ДВА английских термина имеют одинаковое значение – поток: stream thread Thread.
Переменные и основные типы переменных на JAVA Выполнил учитель информатики и ИКТ МБОУ СОШ р.п. Евлашево Горелочкин Н.К.
Исключительные ситуации. Схема обработки исключений Try { //охраняемый блок trow (new MyException();) } catch(MyExeption e) { … } catch(Exeption e){ …
Транксрипт:

Лекция 6

Взаимоблокировка потоков Если имеются два потока и два объекта, подлежащих блокированию, возникает опасность возникновения взаимоблокировки каждый из потоков владеет блокировкой одного объекта и ожидает освобождения другого объекта. Если объект X обладает synchronized-методом, который вызывает synchronized-метод объекта Y, a Y, в свою очередь, также имеет синхронизированный метод, обращающийся к synchronized-методу объекта X, два потока могут находиться в состоянии ожидания взаимного завершения, чтобы овладеть блокировкой, и ни один из них не окажется способен продолжить работу. Такая ситуация называется клинчем.

Рассмотрим пример: class Friends { private Friends partner; private String name; public Friends(String name){ this.name = name; } public synchronized void hug(){ Systern.out.println(Thread.cuгrentThread().getName()+ "в" + name + ".hug() пытается вызвать" + partner.name + ".hugBack()") ; partner.hugBack();} private synchronized void hugBack(){ System.out.println(Thread.currentThread().getName()+ " в " + name + ".hugBack()"); } public void becomeFriend(Friends partner) { this.partner = partner; } }

Далее возможен следующий сценарий: public static void main(string[] args) { final Friends jareth = new Friends("jareth"); final Friends согу = new Friends("cory"); jareth.becomeFriend(согу); cory.becomeFriend(jareth); new Thread(new Runnable(){ public void run(){ jareth.hug();}},thread1).start(); new Thread(new Runnable(){ public void run(){ cory.hug();}},thread2).start();}

Таким образом имеется следующий сценарий: 1)Thread 1 вызывает synchronized метод jareth.hug(); теперь thread 1 владеет блокировкой объекта jareth. 2)Thread 2 вызывает synchronized-метод cory.hug(); теперь thread 2 владеет блокировкой объекта согу; 3)jareth.hug() вызывает synchronized-метод согу.hugBack(); thread 1 приостанавливает выполнение, переходя в стадию ожидания возможности захвата блокировки согу (которой в данный момент владеет thread 2);

4) наконец,cory.hug() вызывает synchronized-метод jareth.hugBack(); thread 2 приостанавливает выполнение, переходя в стадию ожидания возможности захвата блокировки jareth (которой в данный момент владеет thread 1). Программа после запуска успеет вывести: Thread 1 в jareth.hug() пытается вызвать согу.hugBack() Thread 2 в cory.hug() пытается вызвать jareth.hugBack() После программа зависает. Разумеется, не исключено, что повезет, и один из потоков сумеет выполнить код hug целиком еще до момента старта второго потока.

Завершение выполнения потока О потоке, приступившем к работе, говорят как о действующем (alive), и метод isAlive такого потока возвращает значение true. Поток продолжает оставаться действующим до тех пор, пока не будет остановлен в результате возникновения одного из трех возможных событий: метод run завершил выполнение нормальным образом; работа метода run прервана; вызван метод destroy объекта потока.

Возврат из метода run посредством return или в результате естественного завершения кода это нормальный способ окончания выполнения потока. Вызов метода destroy объекта потока это совершенно радикальный шаг. В этом случае поток "умирает" внезапно, независимо от того, что именно он выполняет в данный момент, и не освобождает ни одной из захваченных блокировок, поэтому остальные потоки могут остаться в состоянии бесконечного ожидания.

Многими java машинами метод destroy не поддерживается, и его вызов влечет выбрасывание исключения типа NoSuchMethodError, способного остановить работу потока-инициатора, а не того потока, завершение которого предусматривалось. Корректное завершение работы потока Может возникнуть ситуация, когда поток создается для достижения определенной цели, а затем его выполнение необходимо прервать прежде, чем он решит поставленную задачу.

Для завершения потока вызывается метод interrupt, и код соответствующего потока должен сам следить за событием прерывания и отвечать за его выполнение. Рассмотрим пример: Поток 1 thread2.interrupt() ; Поток 2 while(! interrupted()) { // что-то делается …………………}

Рассмотрим пример: class Main extends Thread { static int howOften; Thread th; String word; Main(String whatToSay) { word = whatToSay; } void setThread(Thread t){th=t;}; public void run() { while(! interrupted()){ th.interrupt(); for (int i = 0; i < howOften; i++) { System.out.println(word); } } } public static void main(String[] args) { howOften = 2; Thread cur = currentThread(); cur.setPriority(Thread.MAX_PRIORITY); Main th1=new Main("Did"); Main th2=new Main("Did Not"); th1.setThread(th2); th2.setThread(th1); th1.start(); th2.start(); }}

Метод interrupt сам по себе не принуждает поток прекращать свою деятельность, хотя часто прерывает спящий режим или ожидание потока, выполняющего соответственно функции sleep или wait. К механизму прерывания работы потока имеют отношения следующие методы: 1)interrupt() - посылает потоку уведомление о прерывании; 2) isInterrupted() - проверяет, была ли прервана работа потока вызовом метода interrupt;

3) interrupted() - статический метод, проверяющий, выполнялось ли прерывание текущего потока, и очищающий "состояние прерывания" потока. Последнее может быть очищено только самим потоком "внешних" способов отмены уведомления о прерывании, посланного потоку, не существует. Прерывание посредством метода interrupt обычно не воздействует на работоспособность потока, но некоторые методы, такие как sleep и wait, будучи прерванными, выбрасывают исключение типа interruptedException.

Другими словами, если поток в момент прерывания его работы с помощью interrupt выполняет один из этих методов, они генерируют исключение InterruptedException. В этом случае состояние прерывания потока очищается, поэтому код, обрабатывающий исключение InterruptedException, обычно должен выглядеть следующим образом: void tick(int count,long pauseTime){ try { for (int i =0; i< count;i++) { System.out.println(…'); Thread.sleep(pauseTime); } } catch(interruptedException e){ Thread.currentThread().interrupt(); } }

Метод tick выводит на экран символ точки count раз, "засыпая" после каждой операции на период времени, равный значению pauseTime, выраженному в миллисекундах. Если работа потока прерывается посредством interrupt в момент выполнения им метода tick, метод sleep выбрасывает исключение типа InterruptedException. Управление передается из цикла for в предложение catch, где уведомление о прерывании потока посылается заново.

Ожидание завершения работы потока Поток способен ждать завершения работы другого потока, используя одну из разновидностей метода join. Рассмотрим пример: class CalcThread extends Thread { private double result; public void run() { result = calculate(); } public double getResult(){return result; } public double calculate() { //... вычислить значение поля result } }

class showjoin { public static void main(string[] args) { CalcThread calc = new CalcThread(); calc.start() ; ………………………….. try{ calc.join(); System.out.println (The result is " + calc.getResult()); } catch (InterruptedException e){ System.out.println(the thread is interrupted"); } } …………………………… }

Выход из join определенно означает, что работа метода CalcThread.run завершена и значение result может быть использовано в текущем потоке. Метод join имеет три формы: 1.public final void join(long millis) throws InterruptedException Ожидает завершения выполнения потока или истечения заданного периода времени, выраженного в миллисекундах, в зависимости от того, что произойдет раньше. Нулевое значение параметра означает задание бесконечного промежутка времени. Если работа потока прерывается в момент ожидания, выбрасывается исключение типа InterruptedException.

2. public final void join(long millis, int nanos) throws InterruptedException Более чувствительная версия метода. Величина интервала ожидания складывается из двух составляющих: millis (выраженной в миллисекундах) и nanos (в наносекундах). Вновь, суммарное нулевое значение параметра означает бесконечное ожидание. Величина nanos должна находиться в промежутке public final void join() throws InterruptedException Метод аналогичен первому варианту при условии join(0). Внутренняя реализация метода join может быть выражена в следующих терминах: while(isAlive()) wait() ;

Потоки – демоны. Существуют два вида потоков пользовательские (user) и потоки - демоны (daemon). Наличие пользовательских потоков сохраняет приложение в работающем состоянии. Когда выполнение последнего из пользовательских потоков завершается, деятельность всех демонов прерывается и приложение финиширует. Прерывание работы демонов похоже на вызов метода destroy оно происходит внезапно и не оставляет потокам никаких шансов для выполнения завершающих операций, поэтому демоны ограничены в выборе функциональных возможностей.

Для придания потоку статуса демона необходимо вызвать метод setDaemon(true), определенный в классе Thread. Проверить принадлежность потока к категории демонов можно с помощью метода isDaemon(). По умолчанию статус демона наследуется потоком от потока-"родителя" в момент создания и после старта не может быть изменен; попытка вызова setDaemon(true) во время работы потока приводит к выбрасыванию исключения типа IIlegalThreadStateException. Метод main по умолчанию порождает потоки со статусом пользовательских.

Рассмотрим пример: class T extends Thread { public void run() { try { if (isDaemon()){ System.out.println("старт потока-демона"); sleep(1000); } else { System.out.println("старт обычного потока"); sleep(10);}} catch (InterruptedException e) { System.out.print("Error" + e); } finally { if (!isDaemon()) System.out.println("завершение работы обычного потока"); else System.out.println("завершение работы потока- демона");}}}

public class DemoDaemonThread { public static void main(String[] args) throws InterruptedException { T tr = new T(); T trdaemon = new T(); trdaemon.setDaemon(true); trdaemon.start(); tr.start();}}

Квалификатор volatile Язык гарантирует, что операции чтения и записи любых значений, кроме относящихся к типам long или double, всегда выполняются атомарным образом соответствующая переменная в любой момент времени будет содержать только то значение, которое сохранено определенным потоком, но не некую смесь результатов нескольких различных операций записи. Однако, атомарный доступ не гарантирует, что поток всегда сможет считать самую последнюю версию значения, сохраненного в переменной. Квалификатор volatile сообщает компилятору, что значение переменной может быть изменено в непредсказуемый момент.

Рассмотрим пример: Int currentvalue = 5; for (;;) { display.showValue(currentvalue); Thread.sleep(1000); // заснуть на одну секунду } Если метод showValue сам по себе не обладает возможностью изменения значения currentvalue, компилятор волен выдвинуть предположение о том, что внутри цикла for это значение можно трактовать как неизменное, и использовать одну и ту же константу 5 на каждой итерации цикла при вызове showValue. Но если содержимое поля currentvalue в ходе выполнения цикла подвержено обновлению посредством других потоков, предположение компилятора окажется неверным. Поэтому правильно объявить переменную currentvalue, используя volatile volatile int currentvalue = 5;

Класс ThreadGroup Потоки могут объединяться в группы потоков по соображениям улучшения управляемости и безопасности. Одна группа потоков может принадлежать другой группе, составляя иерархию с основной группой на верхнем уровне. Потоки, относящиеся к группе, могут управляться единовременно другими словами можно прервать работу сразу всех потоков группы либо установить для них единое максимальное значение приоритета выполнения.

Объекты групп могут быть использованы также для задания верхней границы значений приоритетов потоков, относящихся к группе. После вызова метода setMaxPriority с передачей ему соответствующего наибольшего допустимого значения приоритета любая попытка задания значения, превышающего установленный порог, сводится к повышению приоритета потока только до величины максимального уровня. Рассмотрим пример:

static synchronized void maxThread(Thread thr, int priority){ ThreadGroup grp = thr.getThreadGroup(); thr.setPriority(priority); grp.setMaxPriority(thr.getPriority()- 1); //Мах приоритет в группе. } Класс ThreadGroup поддерживает следующие конструкторы и методы: 1.public ThreadGroup(String name) Создает новый объект класса ThreadGroup, принадлежащий той группе потоков, к которой относится и поток-"родитель". Как и в случае объектов потоков, имена групп не используются исполняющей системой непосредственно, но в качестве параметра name имени группы может быть передано значение null.

2. public ThreadGroup(ТhreadGroup parent, String name) Создает новый объект класса ThreadGroup с указанным именем name в составе "родительской" группы потоков parent. Если в качестве parent передано значение null, выбрасывается исключение типа NullPointerException. 3. public final String getName() Возвращает строку имени текущей группы потоков. 4. public final ThreadGroup getParent() Возвращает ссылку на объект "родительской" группы потоков либо null, если такового нет (последнее возможно только для группы потоков верхнего уровня иерархии).

5. public final void setDaemon(boolean daemon) придает текущему объекту группы потоков статус принадлежности к категории групп-демонов 6. public final boolean isDaemon() Возвращает статус принадлежности текущего объекта группы потоков к категории групп- демонов 7. public final void setMaxPriority(int maxpri) Устанавливает верхнюю границу приоритетов выполнения для текущей группы потоков.

8. public final int getMaxPriority() Возвращает ранее заданное значение верхней границы приоритетов выполнения для текущей группы потоков. 9. public final boolean parentOf(ThreadGroup g) Проверяет, является ли текущая группа "родительской" по отношению к группе g либо совпадает с группой g 10. public final void checkAccess() Выбрасывает исключение типа SecurityException, если текущему потоку не позволено воздействовать на параметры группы потоков; в противном случае просто возвращает управление.

11. public final void destroy() Уничтожает объект группы потоков. Группа не должна содержать потоков, иначе метод выбрасывает исключение типа IllegalThreadStateException. Если в составе группы имеются другие группы, они также не должны содержать потоков. Не уничтожает объекты потоков, принадлежащих группе. 12. public int activeCount() Возвращает приблизительное количество действующих (активных) потоков группы, включая и те потоки, которые принадлежат вложенным группам. Количество нельзя считать точным, поскольку в момент выполнения метода оно может измениться, одни потоки "умирают", а другие создаются. Поток считается действующим, если метод isAlive соответствующего объекта Thread возвращает значение true.

13. public int enumerate( Thread[] threadslnGroup, boolean recurse) Заполняет массив threadslnGroup ссылками на объекты действующих потоков группы, принимая во внимание размер массива, и возвращает количество сохраненных ссылок. Если значение параметра recurse равно false, учитываются только те потоки, которые принадлежат непосредственно текущей группе, а в противном случае еще и потоки, относящиеся ко всем вложенным группам.

14. public int enumerate( Thread[ ] threadslnGroup) метод аналогичен предыдущему при условии enumerate(threadsInGroup true). 15. public int activeGroupCount() подобен методу activeCount, но подсчитывает количество групп, включая вложенные 16. public int enumerate( ThreadGroup[ ] groupslnGroup, boolean recurse) Подобен соответствующему варианту метода enumerate для подсчета потоков, но заполняет массив groupslnGroup ссылками на объекты вложенных групп потоков.

17. public int enumerate( ThreadGroup[ ] groupslnGroup) Метод аналогичен предыдущему при условии enumerate(groupslnGroup, true). 18. public void uncaughtException( Thread thr, Throwable exc) Вызывается, когда поток thr в текущей группе генерирует исключение ехс, которое далее не обрабатывается. В классе Thread существует два статических метода, позволяющих обрабатывать данные о группе, к которой принадлежит текущий поток.

1.public static int activeCount() Возвращает количество действующих потоков в группе, к которой относится текущий поток. 2. public static int enumerate( Thread[ ] threadslnGroup) Метод аналогичен вызову enumerate (threadslnGroup) объекта группы, которой принадлежит текущий поток.

Метод stop() Вызов метода stop приводит к возникновению в соответствующем потоке асинхронного исключения типа ThreadDeath. Объекты типа ThreadDeath могут быть отловлены точно так же, как и другие, а если исключение не подвергается обработке, последствия такого бездействия со временем приведут к аварийному завершению работы потока.

Переменные ThreadLocal Класс ThreadLocal предоставляет возможность иметь единую логическую переменную, обладающую независимыми значениями в контексте каждого отдельного потока. В составе объекта ThreadLocal есть методы set и get, которые позволяют соответственно присваивать и считывать значения переменной для текущего потока. Рассмотрим пример:

public class SomeBuilderDemo { public static class SomeBuilder { private ThreadLocal counter = new ThreadLocal (); public void build() { System.out.println("Thread " + Thread.currentThread().getName() + " Build some structure"); if (counter.get() == null) counter.set(0); counter.set(counter.get() + 1); try { Thread.sleep(100); }catch (InterruptedException e) { e.printStackTrace(); } } public int getCount() {return counter.get();} }

public static class SomeBuilderThread extends Thread { private SomeBuilder builder; public SomeBuilderThread(SomeBuilder builder) { this.builder = builder; } public void run() { for (int i = 0; i < Math.random() * 10; i++) { builder.build(); } System.out.println("My name is " + getName() + "and I built " + builder.getCount() + " things"); }

public static void main(String[] args) { SomeBuilder builder = new SomeBuilder(); Thread thread1 = new SomeBuilderThread(builder); Thread thread2 = new SomeBuilderThread(builder); try { thread1.start(); thread2.start(); thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); }

Результат работы: Thread Thread-1 Build some structure... Thread Thread-0 Build some structure... Thread Thread-1 Build some structure... Thread Thread-0 Build some structure... Thread Thread-0 Build some structure... Thread Thread-1 Build some structure... Thread Thread-0 Build some structure... Thread Thread-1 Build some structure... Thread Thread-1 Build some structure... My name is Thread-0 and I built 4 things Thread Thread-1 Build some structure... Thread Thread-1 Build some structure... My name is Thread-1 and I built 7 things

В строках if (counter.get() == null) counter.set(0); производится ее инициализация. Важно! т.к. ThreadLocal-переменные изолированы в потоках, то инициализация такой переменной должна происходить в том же потоке, в котором она будет использоваться. Ошибкой является инициализация такой переменной - вызов метода set() - в главном потоке приложения, т.к. в данном случае значение, переданное в методе set(), будет "захвачено" для главного потока, и при вызове метода get() в целевом потоке будет возвращен null.

Когда поток прекращает существование, значения, установленные для этого потока в переменных ThreadLocal, недостижимы и могут быть уничтожены сборщиком мусора, если какие-либо ссылки на них отсутствуют. Отладка потоков В составе класса Thread есть несколько методов, которые можно использовать в процессе отладки кода многопоточного приложения.

1.public String toString() Возвращает строковое представление содержимого объекта потока, включающее его наименование, значение приоритета выполнения и имя соответствующей группы потоков. 2. public static void dumpStack() Выводит на консоль данные трассировки для текущего потока. В классе ThreadGroup существую также методы для отладки потоков

1.public String toString() Возвращает строковое представление содержимого объекта группы потоков, включающее его наименование и значение приоритета выполнения. 2. public void list() Выводит на консоль информацию об объекте ThreadGroup, включающую результаты вызовов toString для каждого из потоков, принадлежащих группе, и всех вложенных групп.

Потоки в J2SE 5.0 Добавлены пакеты классов java.util.concurrent.locks, java.util.concurrent, java.util.concurrent.atomic, возможности которых обеспечивают более высокую производительность, масштабируемость, построение потокобезопасных блоков параллельных (concurrent) классов, вызов утилит синхронизации, использование семафоров, ключей и atomic-переменных. Рассмотрим пример:

import java.util.concurrent.Semaphore; public class Main { public static void main(String args[]) throws Exception { //true –гарантия того, что первый поток который вызвал acquire получит доступ к блокируемому объекту Semaphore sem = new Semaphore(1, true); Thread thrdA = new Thread(new MyThread(sem, "Message 1")); Thread thrdB = new Thread(new MyThread(sem, "Message 2")); thrdA.start(); thrdB.start(); thrdA.join(); thrdB.join(); }

class MyThread implements Runnable { Semaphore sem; String msg; MyThread(Semaphore s, String m) { sem = s; msg = m; } public void run() { try { sem.acquire(); System.out.println(msg); Thread.sleep(10); sem.release(); } catch (Exception exc) { System.out.println("Error Writing File"); } } }