Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Текст лекции Многопоточность сокращенный вариан...doc
Скачиваний:
2
Добавлен:
01.03.2025
Размер:
221 Кб
Скачать
☆

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

Поток прекращает работу, когда его метод run возвращает управление. В JDK 1.0 использовался метод stop() , который вызывался из одного потока для завершения работы другого. В настоящее время он не рекомендован к применению.

В текущей версии Java не предусмотрено никакого способа принудительного пре­кращения работы потока. Вместо этого применяется метод interrupt(), который осуществляет запрос на прекращение работы потока.

Когда вызывается метод interrupt(), устанавливается статус прерывания (interrupted status) для потока. Это флаг типа Boolean, который обязательно присут­ствует в любом потоке. В процессе работы поток должен периодически проверять, следует ли ему прекратить выполнение.

Для того чтобы выяснить, установлен ли статус прерывания, необходимо получить объект потока, вызывав метод Thread.currentThread(), а затем обратиться к ме­тоду isInterrupted().

while (!Thread.currentThread().isInterrupted() && дополнительные проверки)

{

требуемые действия

}

Однако, если выполнение потока заблокировано, он не может проверить, следует ли завершить работу. В этом случае на помощь приходит исключение InterruptedException. Когда для блокированного объекта вызывается метод interrupt(), работа блокирующего вызова (метода sleep() или wait()) завершается и управле­ние передается обработчику исключения InterruptedException.

В языке Java нет обязательного требования о завершении работы прерываемого потока. Прерывание просто "привлекает внимание" потока, а последний решает, как отреагировать. Для этого в теле блока catch определяются действия, предпринимае­мые при возникновении исключения InterruptedException. Для некоторых наи­более важных потоков исключение можно игнорировать и продолжать работу. Но в большинстве случаев поток интерпретирует исключение как запрос на прекращение выполнения.

Синхронизация

Когда несколько потоков нуждаются в доступе к разделяемому ресурсу, им необходим некоторый способ гарантии того, что ресурс будет использовать­ся одновременно только одним потоком. Процесс, с помощью которого это достигается, называется синхронизацией. Java обеспечивает поддержку синхронизации — на уровне языка.

Начиная с JDK 5.0, в распоряжении разработчиков есть два механизма защиты фрагментов кода от одновременного обращения различных потоков, это: ключевое слово synchronized и класс ReentrantLock.

Синхронизировать код с помощью synchronized можно двумя способами, первый из которых - это использование синхронизированных методов.

Использование синхронизированных методов

Для защиты фрагментов кода от одновременного обращения различных потоков вызывают метод, который модифицирован ключевым словом synchronized. Пока поток находится внутри синхронизированного метода, все другие потоки, пытающиеся вызвать его (или любой другой синхрони­зированный метод) на том же самом экземпляре объекта, блокированы и должны ждать. При выходе потока из синхронизированного метода (такой участок кода в некоторых источниках называется монитором) блокирование прекращается и следующий ожидаю­щий поток, может войти в вызванный метод.

Чтобы понять потребность в синхронизации, начнем с простого примера, который ее не использует, хотя должен. Следующая программа имеет три простых класса. Первый, Callme, содержит одиночный метод с именем call(). Метод call() имеет параметр типа string с именем msg. Этот метод пытается печатать строку msg внутри квадратных скобок. После того, как call() печатает открывающую скобку и строку msg, он вызывает Thread.sleep(1000), приостанавливающий текущий поток на одну секунду.

Пример в файле UnSynch.java.

// Эта программа не синхронизирована.

class Callme {

void call(String msg) {

System.out.print("[" + msg);

try {

Thread.sleep(1000);

} catch(InterruptedException e) {

System.out.println("Прерывание");

}

System.out.println("]");

}

}

class Caller implements Runnable {

String msg;

Callme target;

Thread t;

public Caller(Callme targ, String s) {

target = targ;

msg = s;

t = new Thread(this);

t.start();

}

public void run() {

target.call(msg);

}

}

class UnSynch {

public static void main(String args[]) {

Callme target = new Callme();

Caller ob1 = new Caller(target, "Привет");

Caller ob2 = new Caller(target, "Синхронизированный");

Caller ob3 = new Caller(target, "Мир");

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

try {

ob1.t.join();

ob2.t.join();

ob3.t.join();

} catch(InterruptedException e) {

System.out.println("Прерывание");

}

}

}

Вывод этой программы:

[Привет[Синхронизированный[Мир]

]

]

Вызывая sleep(), метод call() разрешает переключать выполнение на другой поток. Это приводит к выводу перепутанных строк сообщений. В дан­ной программе нет возможности предохранить три потока от вызова одного и того же метода на одном и том же объекте в одно и то же время. Это из­вестно как состояние состязаний (гонок), потому что три потока состязаются друг с другом, чтобы завершить метод. Представленный пример использо­вал метод sleep(), чтобы сделать эффекты повторяемыми и очевидными. В большинстве ситуаций состояние состязания более тонко и менее пред­сказуемо, потому что вы не можете точно знать, когда произойдет переклю­чение контекста. Это может заставить программу некоторое время выпол­няться правильно, а затем — не правильно.

Чтобы стабилизировать предшествующую программу, нужно сериализовать (преобразовать в последовательную форму) доступ к методу call(). То есть требуется организовать доступ к нему только одного потока одновременно. Для этого нужно просто в начало определения метода call() вставить ключевое слово synchronized, например, как в следующем фрагменте:

class Callme {

synchronized void call(String msg) {

Это предохраняет потоки от входа в call(), пока его использует другой поток. После добавления synchronized вывод программы становится таким:

[Привет]

[Синхронизированный]

[Мир]

(Текст программы в файле Synch.java)

Всякий раз, когда имеется метод (или группа методов), который управляет внутренним состоянием объекта в многопоточной ситуации, нужно использовать ключевое слово synchronized для предотвращения состязаний. Пом­ните, как только поток вызывает синхронизированный метод на некотором экземпляре объекта, никакой другой поток не может вызвать никакой другой син­хронизированный метод на том же самом экземпляре объекта. Однако не синхронизированные методы на этом экземпляре объекта остаются вызываемыми.