Примеры потоков в прошлых статьях представляли поток как последовательный набор операций. То есть поток по сути представлял последовательность инструкций, которые последовательно выполнялись одна за другой. А после выполнения последней операции завершался и поток. Однако нередко имеет место и другая организация потока в виде бесконечного цикла. Например, поток сервера в бесконечном цикле прослушивает определенный порт на предмет получения данных. И в этом случае мы также можем предусмотреть механизм завершения потока.
Распространенный способ завершения потока представляет опрос логической переменной. И если она равна, например, false, то поток завершает бесконечный цикл и заканчивает свое выполнение.
Определим следующий класс потока:
class MyTask implements Runnable {
private boolean isActive;
void disable(){ isActive=false; }
MyTask(){ isActive = true; }
public void run(){
String taskName = Thread.currentThread().getName();
System.out.printf("%s started... \n", taskName);
int counter = 1; // счетчик циклов
while(isActive){
System.out.println("Loop " + counter++);
try{
Thread.sleep(400);
}
catch(InterruptedException _){
System.out.printf("%s interrupted... \n", taskName);
}
}
System.out.printf("%s finished... \n", taskName);
}
}
Переменная isActive указывает на активность потока: пока эта переменная равна true, поток будет продолжать работать. Но с помощью метода disable()
мы можем сбросить состояние этой переменной в false и таким образом завершить поток.
Теперь используем этот класс:
class Program{
public static void main(String[] args) {
System.out.println("Main thread started...");
// создаем и запускаем поток
var myTask = new MyTask();
new Thread(myTask,"MyTask").start();
try{
Thread.sleep(1100);
myTask.disable(); // завершаем поток
Thread.sleep(1000);
}
catch(InterruptedException e){
System.out.println("Main thread interrupted...");
}
System.out.println("Main thread finished...");
}
}
class MyTask implements Runnable {
private boolean isActive;
void disable(){ isActive=false; }
MyTask(){ isActive = true; }
public void run(){
String taskName = Thread.currentThread().getName();
System.out.printf("%s started... \n", taskName);
int counter=1; // счетчик циклов
while(isActive){
System.out.println("Loop " + counter++);
try{
Thread.sleep(400);
}
catch(InterruptedException _){
System.out.printf("%s interrupted... \n", taskName);
}
}
System.out.printf("%s finished... \n", taskName);
}
}
Итак, вначале запускается дочерний поток:
var myTask = new MyTask(); new Thread(myTask,"MyTask").start();
Затем на 1100 миллисекунд останавливаем главный поток "Main thread" и
потом вызываем метод disable(), который переключает в потоке флаг isActive, и дочерний поток завершается.
myTask.disable(); // завершаем поток
Консольный вывод программы:
Main thread started... MyTask started... Loop 1 Loop 2 Loop 3 MyTask finished... Main thread finished...
В языке Java нельзя принудительно завершить поток. Однако с помощью метода interrupt() можно направить запроса на прерывание потока.
При вызове метода interrupt() для потока устанавливается статус INTERRUPTED.
При выполнении работы с помощью метода isInterrupted() поток может периодически проверять, был ли он прерван, и, если прерван, завершать выполняемую работу:
// пока поток не прерван
while (!Thread.currentThread().isInterrupted()){
// делаем некоторую работу
}
Однако, если поток заблокирован, он не может проверить статус с помощью вызова isInterrupted(). Такая ситуация, например, возникает, когда метод interrupt() вызывается для потока, который блокируется вызовом sleep() (а также ряд других, например, методом wait()).
В этом случае блокирующий вызов (например, sleep()) завершается исключением InterruptedException.
Существуют блокирующие вызовы ввода-вывода, которые не могут быть прерваны. Тогда при завершении операции ввода-вывода также генерируется исключение InterruptedException. И в этом случае мы можем
в коде потока обрабатывать исключение:
Runnable r = () -> { // действия потока
try {
// если действия в цикле, в качестве условия проверяем isInterrupted()
while (!Thread.currentThread().isInterrupted()) {
// выполняемые действия
}
}
catch (InterruptedException ex){
// обрабатываем исключение прерывания потока
}
// какие-то завершающие действия при необходимости
};
Хотя, если поток прерван, он не обязан завершаться - он может продолжать работать. Но гораздо чаще поток просто интерпретирует прерывание как запрос на завершение. Например, проэмулируем данную ситуацию:
class MyTask implements Runnable {
public void run(){
String name = Thread.currentThread().getName(); // получаем имя текущего потока
try{
System.out.println(name + " started...");
while(!Thread.currentThread().isInterrupted()){
System.out.println(name + " works");
Thread.sleep(1000); // приостанавливаем поток на 1000 миллисекунд
}
}
catch(InterruptedException ex){
System.out.println(ex.getMessage());
}
System.out.println(name + " finished...");
}
}
public class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
// определяем поток
var t = new Thread(new MyTask(), "MyTask");
// запускаем поток
t.start();
try{
// делаем небольшую задержку, чтобы дочерний поток MyTask успел немного отработать
Thread.sleep(2000);
}
catch(InterruptedException ex){
System.out.println(ex.getMessage());
}
// прерываем поток
t.interrupt();
System.out.println("Main thread finished...");
}
}
В методе run() класса MyTask в цикле while проверяем результат метода isInterupted():
try{
.........................
while(!Thread.currentThread().isInterrupted()){
System.out.println(name + " works");
Thread.sleep(1000); // приостанавливаем поток на 1000 миллисекунд
}
}
catch(InterruptedException ex){
System.out.println(ex.getMessage());
}
В цикле делаем задержку на 1 секунду с помощью Thread.sleep(). Однако если во время этой задержки во вне будет вызван метод interrupt() для этого потока, то мы столкнемся с
исключением InterruptedException. И с помощью конструкции try..catch перехватываем и обрабатываем это исключение.
В методе main запускаем поток MyTask, приостанавливаем главный поток, чтобы MyTask успел немного отработать, и далее вызыаем у MyTask метод interrupt():
var t = new Thread(new MyTask(), "MyTask");
t.start();
try{
Thread.sleep(2000);
}
catch(InterruptedException ex){
System.out.println(ex.getMessage());
}
// прерываем поток
t.interrupt();
В итоге при вызове метода interrupt() во время приостановки MyTask обработает исключение InterruptedException и завершит работу:
Main thread started... MyTask started... MyTask works MyTask works Main thread finished... sleep interrupted MyTask finished...