Menangani InterruptedException di Java

317

Apa perbedaan antara cara penanganan berikut InterruptedException? Apa cara terbaik untuk melakukannya?

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

ATAU

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

EDIT: Saya juga ingin tahu skenario mana yang digunakan keduanya.

devnull
sumber

Jawaban:

436

Apa perbedaan antara cara-cara penanganan InterruptedException berikut ini? Apa cara terbaik untuk melakukannya?

Anda mungkin datang untuk menanyakan pertanyaan ini karena Anda telah memanggil metode yang melempar InterruptedException.

Pertama-tama, Anda harus melihat throws InterruptedExceptionapa itu: Bagian dari tanda tangan metode dan kemungkinan hasil dari memanggil metode yang Anda panggil. Jadi mulailah dengan merangkul fakta bahwa an InterruptedExceptionadalah hasil yang benar-benar valid dari pemanggilan metode.

Sekarang, jika metode yang Anda panggil melempar pengecualian seperti itu, apa yang harus dilakukan metode Anda ? Anda dapat mengetahui jawabannya dengan memikirkan hal-hal berikut:

Apakah masuk akal untuk metode yang Anda laksanakan untuk melempar InterruptedException? Dengan kata lain, apakah InterruptedExceptionhasil yang masuk akal ketika memanggil metode Anda ?

  • Jika ya , maka throws InterruptedExceptionharus menjadi bagian dari tanda tangan metode Anda , dan Anda harus membiarkan pengecualian itu menyebar (yaitu, jangan menangkapnya sama sekali).

    Contoh : Metode Anda menunggu nilai dari jaringan untuk menyelesaikan perhitungan dan mengembalikan hasilnya. Jika pemblokiran panggilan jaringan InterruptedExceptionmetode Anda tidak dapat menyelesaikan perhitungan dengan cara normal. Anda membiarkan InterruptedExceptionpropagate.

    int computeSum(Server server) throws InterruptedException {
        // Any InterruptedException thrown below is propagated
        int a = server.getValueA();
        int b = server.getValueB();
        return a + b;
    }
  • Jika tidak , maka Anda tidak boleh mendeklarasikan metode Anda dengan throws InterruptedExceptiondan Anda harus (harus!) Menangkap pengecualian. Sekarang dua hal penting untuk diingat dalam situasi ini:

    1. Seseorang mengganggu utas Anda. Seseorang mungkin ingin membatalkan operasi, menghentikan program dengan anggun, atau apa pun. Anda harus sopan kepada seseorang itu dan kembali dari metode Anda tanpa basa-basi lagi.

    2. Meskipun metode Anda dapat menghasilkan nilai pengembalian yang masuk akal jika InterruptedExceptionfakta bahwa utas telah terputus mungkin masih penting. Secara khusus, kode yang memanggil metode Anda mungkin tertarik pada apakah gangguan terjadi selama eksekusi metode Anda. Karena itu Anda harus mencatat fakta gangguan yang terjadi dengan mengatur bendera yang terputus:Thread.currentThread().interrupt()

    Contoh : Pengguna telah meminta untuk mencetak jumlah dua nilai. Pencetakan " Failed to compute sum" dapat diterima jika jumlahnya tidak dapat dihitung (dan jauh lebih baik daripada membiarkan program macet dengan jejak tumpukan karena sebuah InterruptedException). Dengan kata lain, tidak masuk akal untuk mendeklarasikan metode ini 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");
         }
    }

Seharusnya sudah jelas bahwa melakukan itu throw new RuntimeException(e)adalah ide yang buruk. Tidak sopan kepada penelepon. Anda bisa membuat pengecualian runtime baru tetapi akar penyebab (seseorang ingin utas menghentikan eksekusi) mungkin hilang.

Contoh lain:

ImplementingRunnable : Seperti yang mungkin telah Anda temukan, tanda tangan Runnable.runtidak memungkinkan untuk dipikirkan kembali InterruptedExceptions. Nah, Anda mendaftar pada implementasi Runnable, yang berarti Anda mendaftar untuk menghadapi kemungkinan InterruptedExceptions. Pilih antarmuka yang berbeda, seperti Callable, atau ikuti pendekatan kedua di atas.

 

MemanggilThread.sleep : Anda mencoba membaca file dan spek mengatakan Anda harus mencoba 10 kali dengan 1 detik di antaranya. Kamu menelpon Thread.sleep(1000). Jadi, Anda harus berurusan dengan InterruptedException. Untuk metode seperti tryToReadFileitu sangat masuk akal untuk mengatakan, "Jika saya terganggu, saya tidak dapat menyelesaikan tindakan saya mencoba membaca file" . Dengan kata lain, sangat masuk akal untuk metode melempar 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;
}

Posting ini telah ditulis ulang sebagai artikel di sini .

aioobe
sumber
Apa masalah dengan metode kedua?
blitzkriegz
4
Artikel itu mengatakan bahwa Anda harus menelepon interrupt()untuk mempertahankan status yang terputus. Apa alasan di balik tidak melakukan ini pada Thread.sleep()?
oksayt
7
Saya tidak setuju, dalam kasus di mana utas Anda tidak mendukung interupsi, setiap interupsi yang diterima menunjukkan kondisi yang tidak terduga (mis. Kesalahan pemrograman, penggunaan API yang buruk), dan membatalkan dengan RuntimeException adalah respons yang sesuai (gagal-cepat ). Kecuali jika itu dianggap sebagai bagian dari kontrak umum utas yang bahkan jika Anda tidak mendukung interupsi, Anda harus melanjutkan operasi ketika Anda menerimanya.
amoe
12
Memanggil metode yang membuang InterruptedExceptions dan mengatakan bahwa Anda "tidak mendukung interupsi" sepertinya pemrograman yang ceroboh dan bug yang menunggu untuk terjadi. Secara berbeda; Jika Anda memanggil metode yang dinyatakan untuk membuang InterruptedException, itu seharusnya tidak menjadi kondisi yang tidak terduga jika metode itu benar-benar melempar pengecualian tersebut.
aioobe
1
@MartiNito, tidak ada, memanggil Thread.currentThread.interrupt()dalam InterruptedExceptionblok catch akan hanya mengatur bendera interupsi. Itu tidak akan menyebabkan orang lain InterruptedExceptionterlempar / terperangkap di InterruptedExceptionblok tangkap luar .
aioobe
97

Kebetulan saya baru saja membaca tentang pagi ini dalam perjalanan untuk bekerja di Java Concurrency In Practice oleh Brian Goetz. Pada dasarnya dia mengatakan kamu harus melakukan satu dari tiga hal

  1. MenyebarkanInterruptedException - Menyatakan metode Anda untuk membuang diperiksa InterruptedExceptionsehingga pemanggil Anda harus menghadapinya.

  2. Pulihkan interupsi - Terkadang Anda tidak dapat melempar InterruptedException. Dalam kasus ini Anda harus menangkap InterruptedExceptiondan mengembalikan status interupsi dengan memanggil interrupt()metode pada currentThreadkode sehingga lebih tinggi tumpukan panggilan dapat melihat bahwa interupsi dikeluarkan, dan dengan cepat kembali dari metode. Catatan: ini hanya berlaku ketika metode Anda memiliki semantik "coba" atau "upaya terbaik", yaitu tidak ada hal penting yang akan terjadi jika metode tersebut tidak mencapai tujuannya. Misalnya, log()atau sendMetric()mungkin metode seperti itu, atau boolean tryTransferMoney(), tetapi tidak void transferMoney(). Lihat di sini untuk detail lebih lanjut.

  3. Abaikan gangguan dalam metode, tetapi kembalikan status saat keluar - mis. Via Guava's Uninterruptibles. Uninterruptiblesambil alih kode boilerplate seperti pada contoh Tugas Noncancelable di JCIP § 7.1.3.
mR_fr0g
sumber
10
"Kadang-kadang Anda tidak dapat membuang InterruptedException" - Saya katakan, kadang-kadang itu tidak sesuai untuk metode untuk propagaet InterruptedExceptions. Formulasi Anda tampaknya menyarankan bahwa Anda harus memikirkan kembali InterruptedException kapan pun Anda bisa .
aioobe
1
Dan jika Anda harus membaca bab 7, Anda akan melihat bahwa ada beberapa seluk-beluk ini, seperti di Listing 7,7, di mana tugas noncancelable tidak mengembalikan interupsi langsung , tetapi hanya setelah itu dilakukan. Goetz juga memiliki banyak rekan penulis untuk buku itu ...
Fizz
1
Saya suka jawaban Anda karena singkat, tetapi saya percaya ini juga harus menyatakan dengan lembut dan cepat kembali dari fungsi Anda dalam kasus kedua. Bayangkan loop panjang (atau tak terbatas) dengan Thread.sleep () di tengah. Tangkapan dari InterruptedException harus memanggil Thread.currentThread (). Interrupt () dan keluar dari loop.
Yann Vo
@YannVo Saya sudah mengedit jawaban untuk menerapkan saran Anda.
leventov
18

Apa yang sedang Anda coba lakukan?

Itu InterruptedExceptiondilemparkan ketika utas sedang menunggu atau tidur dan utas lain menyela itu menggunakan interruptmetode di kelas Thread. Jadi, jika Anda menangkap pengecualian ini, artinya utas telah terputus. Biasanya tidak ada gunanya menelepon Thread.currentThread().interrupt();lagi, kecuali jika Anda ingin memeriksa status "terputus" dari utas dari tempat lain.

Mengenai pilihan Anda yang lain untuk melempar RuntimeException, sepertinya bukan hal yang bijak untuk dilakukan (siapa yang akan menangkap ini? Bagaimana akan ditangani?) Tetapi sulit untuk mengatakan lebih banyak tanpa informasi tambahan.

Grodriguez
sumber
14
Memanggil Thread.currentThread().interrupt()menetapkan bendera yang terputus (lagi), yang memang berguna jika kita ingin memastikan bahwa gangguan tersebut diperhatikan dan diproses pada tingkat yang lebih tinggi.
Péter Török
1
@ Péter: Saya baru saja memperbarui jawaban untuk menyebutkan ini. Terima kasih.
Grodriguez
12

Bagi saya hal utama tentang ini adalah: InterruptedException bukanlah sesuatu yang salah, itu adalah utas yang melakukan apa yang Anda perintahkan. Oleh karena itu, rethrowing yang dibungkus dengan RuntimeException tidak masuk akal.

Dalam banyak kasus masuk akal untuk memikirkan kembali pengecualian yang dibungkus dengan RuntimeException ketika Anda berkata, saya tidak tahu apa yang salah di sini dan saya tidak bisa melakukan apa pun untuk memperbaikinya, saya hanya ingin keluar dari aliran pemrosesan saat ini dan tekan handler pengecualian aplikasi-lebar apa pun yang saya miliki sehingga dapat mencatatnya. Bukan itu masalahnya dengan InterruptedException, itu hanya utas yang menanggapi telah interupsi () memanggilnya, itu melempar InterruptedException untuk membantu membatalkan pemrosesan utas secara tepat waktu.

Jadi sebarkan InterruptedException, atau makan dengan cerdas (artinya di tempat di mana ia akan mencapai apa yang seharusnya dilakukan) dan reset flag interrupt. Perhatikan bahwa flag interrupt akan dihapus ketika InterruptedException dilemparkan; asumsi yang dibuat oleh pengembang Jdk library adalah bahwa menangkap jumlah pengecualian untuk menanganinya, jadi secara default flag dihapus.

Jadi jelas cara pertama lebih baik, contoh yang diposting kedua dalam pertanyaan tidak berguna kecuali jika Anda tidak mengharapkan utas benar-benar terganggu, dan menyela itu berarti kesalahan.

Inilah jawaban yang saya tulis menggambarkan bagaimana interupsi bekerja, dengan sebuah contoh . Anda dapat melihat dalam kode contoh di mana ia menggunakan InterruptedException untuk menyelamatkan dari loop sementara dalam metode run Runnable.

Nathan Hughes
sumber
10

Pilihan default yang benar adalah menambahkan InterruptedException ke daftar lemparan Anda. Interrupt menunjukkan bahwa utas lain ingin utas Anda berakhir. Alasan permintaan ini tidak dibuat jelas dan sepenuhnya kontekstual, jadi jika Anda tidak memiliki pengetahuan tambahan, Anda harus menganggap itu hanya shutdown yang ramah, dan apa pun yang menghindari shutdown adalah respons yang tidak ramah.

Java tidak akan secara acak melempar InterruptedException, semua saran tidak akan memengaruhi aplikasi Anda, tetapi saya telah mengalami kasus di mana pengembang yang mengikuti strategi "menelan" menjadi sangat tidak nyaman. Sebuah tim telah mengembangkan serangkaian besar tes dan menggunakan Thread. Tidur banyak. Sekarang kami mulai menjalankan tes di server CI kami, dan kadang-kadang karena cacat dalam kode akan terjebak dalam menunggu permanen. Untuk membuat situasi lebih buruk, ketika mencoba untuk membatalkan pekerjaan CI itu tidak pernah ditutup karena Thread.Interrupt yang dimaksudkan untuk membatalkan tes tidak membatalkan pekerjaan. Kami harus masuk ke kotak dan mematikan proses secara manual.

Singkatnya, jika Anda hanya membuang InterruptedException Anda cocok dengan maksud default bahwa utas Anda harus diakhiri. Jika Anda tidak dapat menambahkan InterruptedException ke daftar lemparan Anda, saya akan membungkusnya dalam RuntimeException.

Ada argumen yang sangat rasional untuk dibuat bahwa InterruptedException harus menjadi RuntimeException itu sendiri, karena itu akan mendorong penanganan "default" yang lebih baik. Ini bukan RuntimeException hanya karena desainer menempel pada aturan kategoris bahwa RuntimeException harus mewakili kesalahan dalam kode Anda. Karena InterruptedException tidak muncul langsung dari kesalahan dalam kode Anda, itu tidak. Tetapi kenyataannya adalah bahwa sering terjadi InterruptedException muncul karena ada kesalahan dalam kode Anda, (yaitu loop tak berujung, dead-lock), dan Interrupt adalah beberapa metode thread lain untuk menangani kesalahan itu.

Jika Anda tahu ada pembersihan rasional yang harus dilakukan, maka lakukanlah. Jika Anda mengetahui alasan yang lebih dalam untuk Interrupt, Anda dapat mengambil penanganan yang lebih komprehensif.

Jadi secara ringkas pilihan Anda untuk penanganan harus mengikuti daftar ini:

  1. Secara default, tambahkan ke lemparan.
  2. Jika tidak diizinkan menambah lemparan, lempar RuntimeException (e). (Pilihan terbaik dari beberapa opsi buruk)
  3. Hanya ketika Anda mengetahui penyebab eksplisit Interupsi, tangani sesuai keinginan. Jika penanganan Anda sesuai dengan metode Anda, maka setel ulang terganggu oleh panggilan ke Thread.currentThread (). Interrupt ().
nedruod
sumber
3

Saya hanya ingin menambahkan satu opsi terakhir ke apa yang kebanyakan orang dan artikel sebutkan. Seperti yang dinyatakan oleh mR_fr0g, penting untuk menangani interupsi dengan benar dengan:

  • Menyebarkan InterruptException

  • Pulihkan status interupsi di Thread

Atau tambahan:

  • Penanganan kustom Interrupt

Tidak ada yang salah dengan menangani interupsi dengan cara kustom tergantung pada keadaan Anda. Karena interupsi adalah permintaan untuk pengakhiran, sebagai lawan dari perintah yang kuat, sangat sah untuk menyelesaikan pekerjaan tambahan untuk memungkinkan aplikasi menangani permintaan dengan anggun. Misalnya, jika Thread sedang Tidur, menunggu IO atau respons perangkat keras, ketika menerima Interupsi, maka sah untuk menutup koneksi dengan anggun sebelum mengakhiri utas.

Saya sangat merekomendasikan memahami topik ini, tetapi artikel ini adalah sumber informasi yang bagus: http://www.ibm.com/developerworks/java/library/j-jtp05236/

ITIT
sumber
0

Saya akan mengatakan dalam beberapa kasus tidak apa-apa untuk tidak melakukan apa-apa. Mungkin bukan sesuatu yang seharusnya Anda lakukan secara default, tetapi jika seandainya tidak ada cara untuk interupsi terjadi, saya tidak yakin apa yang harus dilakukan (mungkin kesalahan logging, tapi itu tidak mempengaruhi aliran program).

Satu kasus akan jika Anda memiliki antrian tugas (pemblokiran). Jika Anda memiliki Thread daemon yang menangani tugas-tugas ini dan Anda tidak menyela Thread itu sendiri (sepengetahuan saya, jvm tidak menginterupsi thread daemon pada shutdown jvm), saya tidak melihat cara untuk interupsi terjadi, dan oleh karena itu bisa saja diabaikan saja. (Saya tahu bahwa utas daemon dapat dibunuh oleh jvm kapan saja dan karenanya tidak cocok dalam beberapa kasus).

EDIT: Kasus lain mungkin blok yang dijaga, setidaknya berdasarkan pada tutorial Oracle di: http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

Bjarne Boström
sumber
Ini saran yang buruk. Saya sangat kesulitan memikirkan tugas apa pun yang begitu penting sehingga mengabaikan Interupsi. Oracle kemungkinan hanya malas dan tidak ingin menambahkan (lebih) kekacauan pada permohonan Thread.sleep (). Kecuali Anda tahu mengapa utas Anda terganggu, Anda harus menghentikan apa pun yang sedang Anda lakukan (baik kembali, atau menyusun kembali pengecualian untuk membantu utas Anda mati secepat mungkin).
Ajax
Itu sebabnya saya kadang berkata. Juga karena itu belum disarankan dan sangat baik dalam beberapa kasus menurut saya. Saya kira itu tergantung pada jenis perangkat lunak yang Anda bangun, tetapi jika Anda memiliki utas pekerja 24/7 yang seharusnya tidak pernah berhenti (terlepas dari penutupan JVM), saya akan memperlakukan interupsi dari misalnya mengambil item dari BlockingQueue sebagai kesalahan (mis. log) karena "seharusnya tidak terjadi" dan coba lagi. Tentu saya memiliki bendera yang terpisah untuk memeriksa penghentian program. Dalam beberapa program saya, JVM mematikan bukanlah sesuatu yang harus terjadi dalam operasi normal.
Bjarne Boström
Atau dengan kata lain: Menurut pendapat saya lebih baik mengambil risiko memiliki laporan log kesalahan tambahan (dan misalnya mengirim email pada log kesalahan) daripada memiliki aplikasi yang harus berjalan 24/7 untuk berhenti karena persepsi interrupt, yang karenanya Saya melihat tidak ada cara terjadi selama kondisi normal.
Bjarne Boström
Hm, well, kasus penggunaan Anda mungkin berbeda, tetapi di dunia yang ideal, Anda tidak hanya berhenti karena Anda terganggu. Anda berhenti mengambil pekerjaan dari antrian dan membiarkan utas ITU mati. Jika penjadwal utas juga terganggu dan diperintahkan untuk dimatikan, maka JVM akan berakhir, dan permainannya berakhir. Khususnya, jika Anda mengirim kill -9 atau mencoba menurunkan server email Anda atau apa pun, maka itu akan mengabaikan Anda sampai Anda memaksa berhenti, sehingga mencegah bagian lain dari kode Anda dari shutdown anggun. Membunuh secara paksa saat menulis ke disk atau db, dan saya yakin hal-hal indah akan terjadi.
Ajax