Обробка InterruptedException на Java


317

Яка різниця між наступними способами поводження InterruptedException? Який найкращий спосіб це зробити?

try{
 //...
} catch(InterruptedException e) { 
   Thread.currentThread().interrupt(); 
}

АБО

try{
 //...
} catch(InterruptedException e) {
   throw new RuntimeException(e);
}

EDIT: Я також хотів би знати, у яких сценаріях ці два використовуються.


Відповіді:


436

Яка різниця між наступними способами поводження з InterruptException? Який найкращий спосіб це зробити?

Ви, мабуть, прийшли задати це питання, тому що ви назвали метод, який кидає InterruptedException.

Перш за все, ви повинні побачити, throws InterruptedExceptionщо це таке: Частина підпису методу та можливий результат виклику методу, який ви викликаєте. Тож почніть із прийняття факту, що а InterruptedException- це абсолютно дійсний результат виклику методу.

Тепер, якщо метод, який ви викликаєте, кидає такий виняток, що робити з вашим методом? Ви можете з'ясувати відповідь, подумавши про наступне:

Чи має сенс метод, який ви реалізуєте, кинути InterruptedException? Інакше кажучи, чи є InterruptedExceptionрозумним результатом виклик вашого методу?

  • Якщо так , то ви throws InterruptedExceptionповинні бути частиною вашого підпису методу, і ви повинні дозволити винятку розповсюджуватись (тобто взагалі не вловлювати його).

    Приклад : Ваш метод чекає значення мережі, щоб закінчити обчислення та повернути результат. Якщо блокування мережевого виклику кидає, InterruptedExceptionваш метод не може закінчити обчислення нормальним способом. Ви пускаєте InterruptedExceptionпропаганду.

    int computeSum(Server server) throws InterruptedException {
        // Any InterruptedException thrown below is propagated
        int a = server.getValueA();
        int b = server.getValueB();
        return a + b;
    }
  • Якщо ні , то ви не повинні декларувати свій метод за допомогою, throws InterruptedExceptionі ви повинні (повинні!) Зловити виняток. Тепер у цій ситуації важливо пам’ятати дві речі:

    1. Хтось перервав вашу нитку. Що хтось, ймовірно, прагне скасувати операцію, граціозно припинити програму чи що завгодно. Ви повинні бути ввічливими до цього когось і повертатися зі свого методу без подальшої прихильності.

    2. Навіть незважаючи на те, що вашому методу вдасться створити розумне повернене значення у випадку InterruptedExceptionтого, що нитка була перервана, все ще може мати важливе значення. Зокрема, код, який викликає ваш метод, може бути зацікавлений у тому, чи відбулося переривання під час виконання вашого методу. Тому слід зафіксувати факт перерви, встановивши перерваний прапор:Thread.currentThread().interrupt()

    Приклад : Користувач попросив роздрукувати суму двох значень. Друк " Failed to compute sum" є прийнятним, якщо суму неможливо обчислити (і набагато краще, ніж дозволити програмі збій зі слідом стека через " InterruptedException). Іншими словами, не має сенсу декларувати цей метод за допомогою throws InterruptedException.

    void printSum(Server server) {
         try {
             int sum = computeSum(server);
             System.out.println("Sum: " + sum);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();  // set interrupt flag
             System.out.println("Failed to compute sum");
         }
    }

На сьогоднішній день має бути зрозуміло, що просто робити throw new RuntimeException(e)це погана ідея. Це не дуже ввічливо до абонента. Ви можете винайти новий виняток виконання, але першопричина (хтось хоче, щоб нитка зупинила виконання) може загубитися.

Інші приклади:

РеалізаціяRunnable : Як ви, можливо, виявили, підпис Runnable.runне дозволяє повторно скинути InterruptedExceptions. Ну, ви підписалися на реалізацію Runnable, це означає, що ви підписалися, щоб мати справу з можливим InterruptedExceptions. Або виберіть інший інтерфейс, наприклад Callable, або дотримуйтесь другого підходу вище.

 

ДзвінкиThread.sleep : Ви намагаєтеся прочитати файл, і специфікація говорить, що ви повинні спробувати 10 разів з 1 секундою між ними. Ви телефонуєте Thread.sleep(1000). Отже, вам потрібно розібратися InterruptedException. Для такого способу, як tryToReadFileвін має ідеальний сенс сказати: "Якщо мене перерють, я не можу завершити дії, намагаючись прочитати файл" . Іншими словами, для методу метання має ідеальний сенс InterruptedExceptions.

String tryToReadFile(File f) throws InterruptedException {
    for (int i = 0; i < 10; i++) {
        if (f.exists())
            return readFile(f);
        Thread.sleep(1000);
    }
    return null;
}

Ця публікація була переписана як стаття тут .


У чому проблема другого методу?
blitzkriegz

4
У статті йдеться про те, що вам слід закликати interrupt()зберегти перерваний статус. Яка причина не робити цього на Thread.sleep()?
oksayt

7
Я не погоджуюся: у випадку, коли ваш потік не підтримує переривання, будь-які отримані переривання вказують на несподіване стан (тобто помилка програмування, неправильне використання API), а передача даних за допомогою RuntimeException - це відповідна відповідь (невдала швидкість ). Якщо це не вважається частиною загального контракту потоку, що навіть якщо ви не підтримуєте переривання, ви повинні продовжувати роботу, коли ви їх отримуєте.
аме

12
Методи виклику, які кидають InterruptExceptions і кажуть, що ви "не підтримуєте переривання", схожі на неохайне програмування та помилку, що чекає, що трапиться. Ставити по-різному; Якщо ви викликаєте метод, який оголошено, щоб кинути InterruptException, це не має бути несподіваною умовою, якщо цей метод насправді кидає такий виняток.
aioobe

1
@MartiNito, ні, виклик Thread.currentThread.interrupt()у InterruptedExceptionблоці спіймання встановить лише прапор переривання. Це не призведе InterruptedExceptionдо того, що інший буде кинутий / зачеплений у зовнішній InterruptedExceptionблок лову.
aioobe

97

Як це буває, я щойно читав про це сьогодні вранці, коли я збирався працювати в практиці Java Concurrency In Practice від Брайана Геца. В основному він каже, що ви повинні зробити одну з трьох речей

  1. ПоширитиInterruptedException - Оголосіть свій метод, щоб кинути перевірений, InterruptedExceptionщоб ваш абонент мав справу з цим.

  2. Відновити переривання - іноді ви не можете кинути InterruptedException. У цих випадках слід зафіксувати InterruptedExceptionі відновити стан переривання, викликавши interrupt()метод, currentThreadтак що код вище в стопі виклику може побачити, що переривання було видано, і швидко повернутися з методу. Примітка. Це застосовується лише тоді, коли у вашому методі є семантика "спробувати" або "найкраще зусилля", тобто нічого критичного не відбудеться, якщо метод не досягне своєї мети. Наприклад, log()або sendMetric()може бути такий метод, або boolean tryTransferMoney(), але ні void transferMoney(). Дивіться тут для більш детальної інформації.

  3. Ігноруйте переривання в методі, але відновіть стан після виходу - наприклад, через Guava Uninterruptibles. Uninterruptiblesперейміть код котла, як у прикладі Nonc Officeble Task у розділі JCIP § 7.1.3.

10
"Іноді ви не можете кинути InterruptException" - я б сказав, іноді це не підходить для способу пропагувати InterruptExceptions. Здається, ваша формулювання підказує, що ви повинні повторно скидати InterruptedException, коли тільки можете .
aioobe

1
І якщо ви ознайомилися з читанням глави 7, ви побачите, що в цьому є деякі тонкощі, як, наприклад, у Лістингу 7.7, де завдання, яке не працює, не відновлює переривання відразу , а лише після його виконання. Також у Геца є багато співавторів цієї книги ...
Fizz

1
Мені подобається ваша відповідь, тому що вона лаконічна, проте я вважаю, що у другому випадку слід також м'яко та швидко повернутися зі своєї функції. Уявіть собі довгу (або нескінченну) петлю з серединою Thread.sleep (). Улов InterruptException повинен викликати Thread.currentThread (). Interrupt () і вийти з циклу.
Yann Vo

@YannVo Я відредагував відповідь, щоб застосувати вашу пропозицію.
Левентов

18

Що ти намагаєшся зробити?

The InterruptedExceptionкидається, коли нитка чекає або спить, а інша нитка перериває її за допомогою interruptметоду в класі Thread. Тож якщо ви виберете цей виняток, це означає, що нитка була перервана. Зазвичай немає сенсу телефонувати Thread.currentThread().interrupt();ще раз, якщо ви не хочете перевірити "перерваний" стан потоку з іншого місця.

Що стосується іншого варіанту кидка RuntimeException, це, здається, не дуже мудра річ (хто це зробить? Як це впорається?), Але важко сказати більше без додаткової інформації.


14
Виклик Thread.currentThread().interrupt()встановлює перерваний прапор (знову), що дійсно корисно, якщо ми хочемо переконатися, що переривання помічено та обробляється на більш високому рівні.
Péter Török

1
@ Петер: Я тільки оновлював відповідь, щоб згадати про це. Дякую.
Grodriguez

12

Для мене ключовим у цьому є: InterruptException - це не все, що відбувається не так, це те, що робить те, що ви йому сказали. Тому повторне скидання, загорнуте в RuntimeException, має нульовий сенс.

У багатьох випадках має сенс повторно скинути виняток, загорнутий у RuntimeException, коли ви говорите: я не знаю, що тут пішло не так, і я нічого не можу зробити, щоб виправити це, я просто хочу, щоб він вийшов із поточного потоку обробки і натисніть на будь-який обробник винятків для всіх додатків, щоб він міг записати його. Це не так з InterruptException, це просто потік, що відповідає на переривання () на нього, він кидає InterruptException, щоб вчасно скасувати обробку потоку.

Тож поширюйте InterruptException або їжте його розумно (маючи на увазі місце, де воно здійснить те, що було призначено) та встановіть прапор переривання. Зверніть увагу, що прапор переривання видаляється, коли InterruptException викидається; припущення, яке розробники бібліотеки Jdk роблять, полягає в тому, що пошук винятків означає обробку з ним, тому за замовчуванням прапор очищається.

Тож, безумовно, перший спосіб кращий, другий розміщений приклад у запитанні не корисний, якщо ви не очікуєте, що нитка насправді перерветься, а переривання її означає помилку.

Ось відповідь, яку я написав, описуючи, як працюють переривання, на прикладі . Ви можете бачити в прикладі коду, де використовується метод InterruptException, щоб уникнути циклу часу в методі запуску Runnable.


10

Правильний вибір за замовчуванням - додати InterruptException до списку кидків. Переривання означає, що інша нитка бажає, щоб ваша нитка закінчилася. Причина цього запиту не стає очевидною і є цілком контекстуальною, тому якщо у вас немає додаткових знань, слід припустити, що це просто дружнє відключення, і все, що дозволяє уникнути цього відключення, є недоброзичливою відповіддю.

Java не випадково викине InterruptException, всі поради не вплинуть на вашу програму, але я зіткнувся з випадком, коли розробник дотримуючись стратегії "проковтування" став дуже незручним. Команда розробила великий набір тестів і багато використовувала Thread.Sep. Тепер ми почали проводити тести на нашому сервері CI, і іноді через дефекти коду застрягли б у постійному очікуванні. Щоб погіршити ситуацію, при спробі скасувати завдання CI він ніколи не закривався, оскільки Thread.Interrupt, який мав на меті перервати тест, не припиняв роботу. Нам довелося увійти до коробки та вручну вбити процеси.

Таким чином, короткий випадок, якщо ви просто кинете InterruptException, ви співпадаєте з наміром за замовчуванням, який має закінчитися. Якщо ви не можете додати InterruptException до свого списку кидків, я б загорнув його у RuntimeException.

Слід зробити дуже раціональний аргумент, що InterruptException має бути самим RuntimeException, оскільки це сприятиме кращому керуванню "за замовчуванням". Це не RuntimeException лише тому, що дизайнери дотримувались категоричного правила, що RuntimeException має представляти помилку у вашому коді. Оскільки InterruptException не виникає безпосередньо з помилки у вашому коді, це не так. Але реально така ситуація, що часто виникає InterruptException, оскільки у вашому коді є помилка (тобто нескінченна петля, мертве блокування), а Переривання - це якийсь інший метод потоку для вирішення цієї помилки.

Якщо ви знаєте, що потрібно зробити раціональну очистку, то зробіть це. Якщо ви знаєте більш глибоку причину переривання, ви можете взяти на себе більш всебічне управління.

Отже, підсумовуючи, ваш вибір для обробки повинен відповідати цьому списку:

  1. За замовчуванням додайте до кидків.
  2. Якщо заборонено додавати до кидків, киньте RuntimeException (e). (Кращий вибір декількох поганих варіантів)
  3. Тільки коли ви знаєте явну причину переривання, обробляйте, як бажано. Якщо ваша обробка відповідає вашому методу, то скидання перерветься викликом до Thread.currentThread (). Interrupt ().

3

Я просто хотів додати останній варіант до того, що згадує більшість людей та статей. Як заявив mR_fr0g, важливо правильно поводитися з перериванням:

  • Поширення InterruptException

  • Відновити стан переривання на потоці

Або додатково:

  • Спеціальна робота з Interrupt

Немає нічого поганого в тому, щоб обробляти переривання користувацьким способом залежно від ваших обставин. Оскільки переривання - це запит про припинення, на відміну від силової команди, цілком справедливо виконати додаткові роботи, щоб програма змогла грамотно обробляти запит. Наприклад, якщо Нитка спить, очікує на IO або апаратну відповідь, коли вона отримує Переривання, то цілком справедливо граціозно закрити будь-які з'єднання перед припиненням потоку.

Я настійно рекомендую розібратися в цій темі, але ця стаття є гарним джерелом інформації: http://www.ibm.com/developerworks/java/library/j-jtp05236/


0

Я б сказав, що в деяких випадках нормально нічого не робити. Напевно, це не те, що ви повинні робити за замовчуванням, але у випадку, якщо не повинно бути жодного способу переривання, я не впевнений, що ще робити (можливо, помилка реєстрації, але це не впливає на потік програми).

Один випадок буде, якщо у вас є черга (блокування) черги. Якщо у вас є демонова нитка, яка впорається з цими завданнями, і ви не перериваєте нитку самостійно (наскільки мені відомо, jvm не перериває демон-нитки при відключенні jvm), я не бачу способу, щоб переривання відбулося, і тому це може бути просто проігноровано. (Я знаю, що демонова нитка може бути вбита jvm в будь-який час, і тому в деяких випадках непридатна).

EDIT: Інший випадок може бути захищеним блоком, принаймні на основі підручника Oracle за адресою: http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html


Це погана порада. У мене вкрай неприємні роздуми про будь-яке завдання, яке так важливо, що ігнорувати Перерви. Oracle, ймовірно, просто ледачий і не хотів додавати (більше) безладу до виклику Thread.sleep (). Якщо ви не знаєте, чому ваша нитка переривається, вам слід припинити все, що ви робите (або повертайтеся, або повторно викидаєте виняток, щоб допомогти вашій нитці загинути якнайшвидше).
Аякс

Ось чому я казав іноді. Також тому, що це ще не було запропоновано, і воно, на мою думку, в деяких випадках ідеально . Я думаю, це залежить від того, яке програмне забезпечення ви будуєте, але якщо у вас є робоча нитка 24/7, яка ніколи не зупинятиметься (крім відключення JVM), я б трактував перебої, наприклад, прийняття елемента з BlockingQueue як помилку (тобто журнал), оскільки "це не повинно статися", і спробуйте ще раз. Зрозуміло, я мав би окремі прапори, щоб перевірити завершення програми. У деяких моїх програмах відключення JVM - це не те, що повинно відбуватися при нормальній роботі.
Bjarne Boström

Або кажучи іншим способом: на мою думку, краще ризикувати виникненням додаткових звітів журналу помилок (наприклад, надсилання пошти в журнали помилок), ніж наявність програми, яка повинна працювати 24/7, щоб зупинити через переривання прийому, для якого Я не бачу, як це відбувається в звичайних умовах.
Bjarne Boström

Гм, ну ваші випадки використання можуть відрізнятися, але в ідеальному світі ви не просто припиняєте, оскільки вас перебили. Ви перестаєте знімати роботу з черги і даєте ВІННІТЬ ТИМ. Якщо планувальник потоків також буде перерваний і сказаний на вимкнення, JVM припиняється, і його гра закінчується. Зокрема, якщо ви надсилаєте kill -9 або іншим чином намагаєтесь зняти ваш поштовий сервер чи що завгодно, то він буде ігнорувати вас, поки ви не змусите його відмовитися, тим самим запобігаючи іншій частині вашого коду від витонченого відключення. Примушуйте вбивати під час запису на диск або db, і я впевнений, що трапляться прекрасні речі.
Аякс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.