Статьи

Работа с перерывами

Я просто смотрел интервью VJUG с Хайнцем Кабуцем, которое вдохновило меня написать пост о прерываниях. Кстати, я бы порекомендовал подписаться на канал VJUG на YouTube — очень информативно.

Хайнц — это всегда хорошая ценность, и трудно смотреть какие-либо его презентации, не многому научившись. Он поднял тему о том, как обращаться с InterruptedException и предположил, что немногие Java-программисты правильно с ним справляются. Лучшее объяснение прерываний потоков, которое я прочитал, содержится в моей любимой книге по Java — параллелизм на практике на практике (p138-144). Если вы прочитали эти страницы, вы будете знать, как правильно обращаться с InterruptedException 🙂

Вот краткое резюме:

Как часто вы сталкивались с этим кодом:

1
2
3
4
5
6
7
.......
try {
   Thread.sleep(1000);
} catch(InterruptedException e){
   e.printStackTrace();
}
......

Процесс должен спать на секунду, но «досадно» должен иметь дело с InterruptedException . Разработчик не знает, что делать с этим исключением, поэтому просто регистрирует его на консоли.

Это очень плохая практика! Если вы уверены, что ваш поток никогда не прервется (вы пишете этот код в закрытой системе), то вам, вероятно, следует сделать что-то вроде выброса AssertionError в блоке catch с комментарием, что это никогда не должно произойти. Если вообще возможно, что поток может быть прерван, вам нужно правильно обработать это прерывание.

Поток можно прервать, вызвав его метод interrupt() . Это установит его статус прерывания в true и, следовательно, при вызове isInterrupted() вернет true. Когда interrupt() вызывается определенными методами блокировки, такими как Thread.sleep() InterruptedException . Обратите внимание, что при срабатывании InterruptedException будет установлено на false. В Thread есть метод, называемый interrupted() который, подобно isInterrupted() возвращает состояние прерывания потока, но крайне важно вернуть статус прерывания в значение false. ( interrupted() — метод с очень странным именем для того, что он делает …)

Мы можем увидеть все это на работе в следующем примере:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package util;
 
/**
 * Created by daniel on 16/04/15.
 */
public class Interrupt {
    public static void main(String[] args) {
        Thread sleeperThread = new Thread(){
          public void run(){
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  System.out.println(isInterrupted()); //prints false
                  interrupt();
                  System.out.println(isInterrupted()); //prints true
                  System.out.println(interrupted()); //prints true
                  System.out.println(isInterrupted()); //prints false
              }
          }
        };
 
        sleeperThread.start();
        sleeperThread.interrupt();
    }
}

Процитируем параллелизм Java на практике:

«В спецификации API или языка нет ничего, что связывало бы прерывание с какой-либо конкретной семантикой отмены, но на практике использование прерывания для чего угодно, кроме отмены, хрупко и трудно поддерживать в больших приложениях».

Другими словами, прерывание — это просто сигнал. Теоретически вы можете использовать механизм прерываний, чтобы указать потоку делать все, что вы хотите, возможно, выполнить действие A вместо B — но мы против этого.

1
2
3
4
5
6
7
8
9
.......
try {
   Thread.sleep(1000);
} catch(InterruptedException e){
   actionA();
   return;
}
actionB();
......  

Итак, как правильно бороться с прерыванием. Ну, это немного зависит от вашего кода. Давайте предположим, что мы используем прерывание «правильно» в качестве отмены, и ваш код ожидает отмены (это должно быть указано в документации), тогда ваш код должен отменить свои действия контролируемым образом. То, что выдается исключение, не означает, что вы должны спешно выходить, оставляя позади себя беспорядок Поскольку вы имели дело с прерыванием, нет необходимости восстанавливать состояние прерывания в потоке.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public Task getNextTask(BlockingQueue<Task> queue){
    boolean interrupted = false;
    try{
        while(true){
            try{
                return queue.take();
            }catch(InterruptedException e){
                interrupted = true;
                //retry
            }
        }
    }finally {
        if(interrupted){
            Thread.currentThread().interrupt();
        }
    }
}
Ссылка: Работа с прерываниями от нашего партнера по JCG Дэниела Шайя в блоге Rational Java .