Главная

Популярная публикация

Научная публикация

Случайная публикация

Обратная связь

ТОР 5 статей:

Методические подходы к анализу финансового состояния предприятия

Проблема периодизации русской литературы ХХ века. Краткая характеристика второй половины ХХ века

Ценовые и неценовые факторы

Характеристика шлифовальных кругов и ее маркировка

Служебные части речи. Предлог. Союз. Частицы

КАТЕГОРИИ:






Достоинства концепции монитор




В общем случае monitor решает следующие проблемы:

- взаимное исключение (mutual exclusion) никакие два и более активных потоков не могут находиться одновременно в синхронизированной по одному объекту секции.

- условное ожидание (condition waiting) было реализовано в ограниченном буфере. Потоки могут быть переведены в состояние ожидания методом wait() и пробудиться при вызове методов notify()/notifyAll() при выполнении определенного условия.

- видимость (visibility) синхронизированная секция или метод позволяют организовать работу потоков с данными таким образом, чтобы любой из потоков мог видеть самые последние значения переменной.

Прерывание работающего потока. Методы interrupt() и isInterrupted()

В предыдущих разделах мы научились дожидаться завершения другого потока методом join(). Сейчас рассмотрим, как можно прервать уже работающий поток. Подобная задача может быть актуальна для многих приложений, в которых необходима из одного работающего потока прервать выполнение других запущенных потоков. Например, из потока браузера по нажатию пользователем кнопки Esc приостановить загрузку картинки в HTML страничку, выполняющуюся в отдельном потоке. Сразу отметим, что эта задача нетривиальная и сложная.

Для прерывания потоков существует несколько методов Java обозначенных аннотацией @deprecated, что говорит о том, что методы использовать нежелательно и возможно скоро их удалят из jdk. Методы считаются опасными для использования, так как они не являются в полной мере “потокобезопасными”. К данным методам, уничтожающим поток, относятся следующая группа методов: destroy(), stop(), suspend() и resume(). Рассмотрим их подробнее в конце раздела.

Вместо deprecated методов были добавлены другие: interrupt(), isInterrupted() и interrupted().

Рассмотрим примеры использования методов:

public class Example {

public static void main(String[] args)

throws InterruptedException{

Thread thread = new Thread (new Runnable(){

public void run() {

Thread myThread = Thread.currentThread();

while (true) {

System.out.println(myThread.isInterrupted());

for (long k = 0; k < 1_000_000_000L; k++);

}

}

});

thread.start();

thread.sleep(1000);

thread.interrupt();

}}

Результат выполнения:

false

false

true

true

true

true

true

… очень долго

 

В программе в потоке main был создан другой поток thread, который в дальнейшем был запущен методом start(). После запуска потока thread, поток main спал 1000 мс., а далее для потока thread был вызван метод interrupt(). Метод interrupt() прерывает выполнение потока. Схематично данная последовательность событий может быть представлена следующим образом:

 

В отличие от блокирующего метода join() при вызове неблокирующего метод interrupt() не ожидается завершение выполнения потока, а он может быть прерван сразу же.

Внутри метода run() потока thread в бесконечном цикле выводится на экран состояние потока с помощью нестатического логического метода isInterrupted(). Данный метод возвращает значение true, если поток прерван методом interrupt() и false в противном случае. Таким образом, в Java каждый поток имеет некий булевский флажок, который при старте потока равен false, а при вызове метода interrupt() становится равен true. Чтобы значения флажка выводились с некоторым интервалом времени, в программе используется дополнительный цикл, перебирающий значения переменной-параметра от 0 до 1 000 000 000.

Вернуть флаг прерванности текущего потока можно также с помощью статического метода interrupted().

public class Example {

public static void main(String[] args)

throws InterruptedException{

Thread thread = new Thread (new Runnable(){

public void run() {

while (true) {

System.out.println(Thread.interrupted());

for (long k = 0; k < 1_000_000_000L; k++);

}

}

});

thread.start();

thread.sleep(1000);

thread.interrupt();

}}

Результат выполнения:

false

true

false

false

false

false

false

false

false

… очень долго

Как вы можете видеть по результату выполнения программы, хотя методы isInterrupted() и interrupted() предназначены для одного и того же, они имеют отличия. Метод isInterrupted() – не очищает флаг, а метод interrupted() – очищает. Кроме того метод isInterrupted() более верен с точки зрения соглашения об именовании и code conventions.

Рассмотрим пример, демонстрирующий применения флага прерывания.

public class Example {

public static void main(String[] args)

throws InterruptedException{

Thread thread = new Thread (new Runnable(){

public void run() {

Thread myThread = Thread.currentThread();

while (!myThread.isInterrupted()) {

System.out.println("Hello! ");

for (long k = 0; k < 1_000_000_000L; k++);

}

}

});

thread.start();

thread.sleep(1000);

thread.interrupt();

}}

В методе run() потока организован цикл, условием завершения которого является прерывание потока. Пока поток не прерван на экран будет выводиться текст Hello!

Обратите внимание, что метод interrupt() прерывает работу потока не принудительно. Флаг прерывания сигнализирует о том, что работа потока прервана, но этот флаг нужно еще прочитать. Может возникнуть ситуация, что поток долго выполняет свою работу, и все это время не читает флаг. Поэтому целесообразнее дробить работу на короткие по времени этапы, и после завершения каждого проверять состояния флага прерывания. Поэтому часто используют другой механизм прерывания.

Ранее уже было отмечено, что большинство блокирующих методов (например, метод sleep()) бросает проверяемое исключение InterruptedException. Если флаг прерывания у потока установлен, и он пытается вызвать блокирующий метод, то автоматически вылетит исключение InterruptedException, по которому работа потока завершится.

Рассмотрим следующий пример:

public class Example {

public static void main(String[] args)

{

Thread.currentThread().interrupt();

try{

Thread.sleep(Long.MAX_VALUE);

} catch (InterruptedException e) {

System.out.println("Interrupted by exception ");

}

}}

В примере поток main сам для себя вызывает метод interrupt() по которому устанавливается флаг прерывания. Внутри потока флаг не проверяется, поток продолжает выполняться. Однако когда поток попытается заснуть, т.е. вызвать метод sleep() будет автоматически брошено исключение InterruptedException, т.к. осуществляется попытка вызвать блокирующий метод при установленном флаге прерывания. Выполнение потока завершится. На экране появится текст Interrupted by exception.

Можно попытаться обмануть JVM – добавить метод interrupted(). Данный метод очистит флаг прерывания и поток уснет.

 

public class Example {

public static void main(String[] args)

{

Thread.currentThread().interrupt();

try{

Thread.interrupted();

Thread.sleep(Long.MAX_VALUE);

} catch (InterruptedException e) {

System.out.println("Interrupted by exception ");

}

}}

Как вы видите принудительно остановить поток практически нельзя.

Вернемся к исключению InterruptedException. Рассмотрим еще один вариант, когда можно остановить поток с помощью данного исключения. Дело в том, что если поток выполняет блокирующий метод (либо спит по sleep(), либо ожидает по wait()) и в это время для потока выполняется метод interrupt(), то в этом случае автоматически бросается исключение InterruptedException. Таким образом, не имеет значения был ли установлен флаг прерывания до вызова блокирующего метода или позже, когда блокирующий метод уже выполнялся, в любом случае вылетит исключение InterruptedException.

public class Example {

public static void main(String[] args)

throws InterruptedException{

Thread thread = new Thread (new Runnable(){

public void run() {

try{

System.out.println("I will sleep ");

Thread.sleep(Long.MAX_VALUE);

} catch (InterruptedException e) {

System.out.println("I interrupted by exception");

}

}

});

thread.start();

Thread.sleep(1000);

thread.interrupt();

}}

 

Результат:

I will sleep

… через секунду

I interrupted by exception

 

В примере main стартовал новый поток, который заснул. При этом метод main() продолжил свою работу, поспал 1 сек., и вызвал для запущенного потока метод interrupt(). После этого сразу же вылетит исключение InterruptedException и поток прервется.

Более жестко прервать выполнение потока можно с использованием deprecated методов destroy() и stop(), они обеспечивают другой способ прерывания. Методы сейчас являются запрещенными к применению. Так метод destroy() должен был сразу же по определению убить поток. В спецификации не было сказано, как завершит работу поток, будет ли это завершение корректным. Сейчас метод destroy() не реализован. При попытке его использовать всегда вылетает исключение NoSuchMethodError.

Метод stop() реализован в Java и убивает поток более мягко, чем это делал метод destroy(). При вызове метода stop() у потока в случайном месте вылетает unchecked исключение ThreadDeath - наследник Error, нарушающий соглашение об именовании. Это исключение можно перехватить и корректно завершить работу потока.

public class Example {

public static void main(String[] args)

throws InterruptedException{

Thread thread = new Thread (new Runnable(){

public void run() {

try{

while (true) {

System.out.println("Hello! ");

for (long k = 0; k < 1_000_000_000L; k++);

}

} catch (ThreadDeath e) {System.out.println(e);}

// можно даже продолжить работу потока!!!

}

});

thread.start();

Thread.sleep(1000);

thread.stop();

}}

То, что метод stop() вызывает исключение – одно из важных его преимуществ перед методом destroy(). Представьте себе, что поток захватил синхронизированную секцию и его прервали. Если его прервали методом stop(), то вылетит исключение и по нему поток будет выброшен из синхронизированной секции. Если бы поток был прерван методом destroy(), то он бы исчез моментально, оставив заблокированной секцию, т.е. блокировка не была бы снята и никакой другой поток не смог бы войти в эту синхронизированную секцию.

Еще два deprecated метода – это suspend() и resume(), которые не убивают, а приостанавливают поток. По вызову метода suspend() поток приостанавливается, а вызов метода resume() снова запускает поток.

public class Example {

public static void main(String[] args)

throws InterruptedException{

Thread thread = new Thread (new Runnable(){

public void run() {

while (true) {

System.out.println("Hello! ");

for (long k = 0; k < 1_000_000_000L; k++);

}

}

});

thread.start();

Thread.sleep(3000);

thread.suspend();

Thread.sleep(3000);

thread.resume();

}}

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

Потоки-демоны работают в фоновом режиме вместе с программой, но не являются неотъемлемой частью программы. Если какой-либо процесс может выполняться на фоне работы основных потоков выполнения и его деятельность заключается в обслуживании основных потоков приложения, то такой процесс может быть запущен как поток-демон. Например, процесс автосохранения документа. С помощью метода setDaemon(boolean value), вызванного вновь созданным потоком до его запуска, можно определить поток-демон. Метод boolean isDaemon() позволяет определить, является ли указанный поток демоном или нет.

class T extends Thread {

public void run() {

try {

if (isDaemon()){

System.out.println("старт потока-демона");

sleep(10000);

} else {

System.out.println("старт обычного потока");

}

} catch (InterruptedException e) {

System.err.print("Error" + e);

} finally {

if (!isDaemon())

System.out.println(

"завершение обычного потока");

else

System.out.println(

"завершение потока-демона");

}

}

}

 

public class DemoDaemonThread {

public static void main(String[] args) {

T usual = new T();

T daemon = new T();

daemon.setDaemon(true);

daemon.start();

usual.start();

System.out.println("последний оператор main");

}

}

В результате компиляции и запуска, возможно, будет выведено:

последний оператор main

старт потока-демона

старт обычного потока

завершение обычного потока

Поток-демон (из-за вызова метода sleep(10000)) не успел завершить выполнение своего кода до завершения основного потока приложения, связанного с методом main(). Базовое свойство потоков-демонов заключается в возможности основного потока приложения завершить выполнение потока-демона (в отличие от обычных потоков) с окончанием кода метода main(), не обращая внимания на то, что поток-демон еще работает. Если уменьшать время задержки потока-демона, то он может успеть завершить свое выполнение до окончания работы основного потока.

 

На самостоятельнее изучение: приоритеты потоков (как по мне, то вещь мало нужная), потоки в графических приложениях и апплетах (в конспекте), пакеты java.util.concurrent.locks, java.util.concurrent.atomic, java.util.concurrent, добавленные в Java 5 (нужно, но сложно и объемно!).

Проверь себя!!!

Самые распространенные вопросы на собеседовании:

Multithreading

1. Каким образом можно создать поток?

2. Какие способы синхронизации в Java?

3. Как работают методы wait() и notify()/notifyAll()?

4. Чем отличается работа метода wait() с параметром и без параметра?

5. Как работает метод Thread.yield()? Чем отличаются методы Thread.sleep() и Thread.yield()?

6. Чем отличаются методы Thread.sleep() и Object.wait()?

7. Как работает метод Thread.join()?

8. Что такое dead lock?

9. Как правильно завершить работу потока? (Иногда говорят убить поток).

10. На каком объекте происходит синхронизация при вызове static synchronized метода?

11. Для чего используется ключевое слово volatile?

 


[1] Некоторые алгоритмы основаны на кванте времени обработки запроса процессором, чем квант меньше, тем раньше эта инструкция в очереди на обработку.

[2] Кого интересует происхождение проблемы, читайте статью Herb Sutter "The Free Lunch Is Over" http://www.gotw.ca/publications/concurrency-ddj.htm, опубликованную в 2005 году.

[3] Интерфейс Runnable – это пример GoF шаблона (паттерна) Command. Если кратко, то шаблон четко определяет что следует вызвать и как, но недоопределяет когда. Например, когда вы пишете в программе строку System.out.println(10); то сразу определяете, что, как и когда вызвать. Эту же строку можно записать в соответствии с шаблоном Command следующим образом:

public class Example {

public static void main (String[] args){

Runnable command = new PrintRunnable(10); //откладываем вызов

command.run();//вызов команды

}}

 

class PrintRunnable implements Runnable{

private int k=10;

PrintRunnable(int k) {

this.k=k;

}

public void run() {

System.out.println(k);

}}

Мы создали объект команды command, но когда будет вызван метод run(), тогда она и выполнится. Преимущества паттерна состоит в том, что удается четко разделить, что надо делать и когда надо делать.

 

[4] Не путайте с потоками ввода-вывода InputStream






Не нашли, что искали? Воспользуйтесь поиском:

vikidalka.ru - 2015-2024 год. Все права принадлежат их авторам! Нарушение авторских прав | Нарушение персональных данных