Secara historis, Java telah mencoba menyediakan interupsi terbatas preemptif, tetapi ada banyak masalah, seperti Thread.stop, Thread.suspend, dan Thread.resume yang ditinggalkan yang diperkenalkan sebelumnya. Di sisi lain, karena pertimbangan ketahanan kode aplikasi Java, ambang pemrograman diturunkan dan kemungkinan pemrogram yang tidak mengetahui mekanisme yang mendasarinya secara tidak sengaja merusak sistem berkurang.
Saat ini, penjadwalan thread Java tidak menyediakan interupsi preemptif, namun menggunakan interupsi kooperatif. Sebenarnya prinsip interupsi kooperatif sangat sederhana, yaitu melakukan polling pada tanda tertentu yang menunjukkan adanya interupsi.
Misalnya kode berikut:
volatil bool Terganggu;
//…
while(!terganggu) {
menghitung();
}
Namun, masalah kode di atas juga terlihat jelas. Ketika waktu eksekusi komputasi relatif lama, interupsi tidak dapat direspon tepat waktu. Di sisi lain, dengan menggunakan polling untuk memeriksa variabel flag, tidak ada cara untuk menghentikan operasi pemblokiran thread seperti wait dan sleep.
Jika Anda masih menggunakan ide di atas, jika Anda ingin interupsi ditanggapi tepat waktu, Anda harus memeriksa variabel mark melalui penjadwalan thread di bagian bawah mesin virtual. Ya, ini memang dilakukan di JVM.
Berikut ini disarikan dari source code java.lang.Thread:
boolean statis publik terputus() {
kembalikan CurrentThread().isInterrupted(true);
}
//…
boolean asli pribadi isInterrupted(boolean ClearInterrupted);
Dapat ditemukan bahwa isInterrupted dideklarasikan sebagai metode asli, yang bergantung pada implementasi JVM yang mendasarinya.
Faktanya, JVM mempertahankan flag interupsi secara internal untuk setiap thread. Namun, aplikasi tidak dapat mengakses variabel interupsi ini secara langsung dan harus beroperasi melalui metode berikut:
kelas publik Utas {
//Tetapkan tanda interupsi
interupsi kekosongan publik() { ... }
//Dapatkan nilai tanda interupsi
boolean publik isInterrupted() { ... }
//Hapus tanda interupsi dan kembalikan nilai tanda interupsi terakhir
boolean statis publik terputus() { ... }
}
Biasanya, memanggil metode interupsi dari sebuah thread tidak langsung menyebabkan interupsi, tetapi hanya menyetel flag interupsi di dalam JVM. Oleh karena itu, dengan mencentang tanda interupsi, aplikasi dapat melakukan sesuatu yang istimewa atau mengabaikan interupsi sepenuhnya.
Anda mungkin berpikir bahwa jika JVM hanya menyediakan mekanisme interupsi kasar ini, pada dasarnya JVM tidak memiliki keunggulan dibandingkan dengan metode aplikasi itu sendiri dalam mendefinisikan variabel interupsi dan polling.
Keuntungan utama dari variabel interupsi internal JVM adalah ia menyediakan mekanisme untuk mensimulasikan "perangkap interupsi" otomatis untuk situasi tertentu.
Saat menjalankan panggilan pemblokiran yang melibatkan penjadwalan thread (seperti menunggu, tidur, dan bergabung), jika terjadi gangguan, thread yang diblokir akan menampilkan InterruptedException "secepat mungkin". Oleh karena itu, kita dapat menggunakan kerangka kode berikut untuk menangani interupsi pemblokiran thread:
mencoba {
//tunggu, tidur atau bergabung
}
menangkap(InterruptedException e) {
//Beberapa pekerjaan penanganan interupsi
}
Dengan "secepat mungkin", saya kira JVM memeriksa variabel interupsi di celah antara penjadwalan thread. Kecepatannya tergantung pada implementasi JVM dan kinerja perangkat keras.
Namun, untuk operasi pemblokiran thread tertentu, JVM tidak secara otomatis menampilkan InterruptedException. Misalnya, operasi I/O tertentu dan operasi kunci internal. Untuk jenis operasi ini, interupsi dapat disimulasikan dengan cara lain:
1) I/O soket asinkron di java.io
Saat membaca dan menulis soket, metode baca dan tulis InputStream dan OutputStream akan memblokir dan menunggu, tetapi tidak akan merespons interupsi Java. Namun, setelah memanggil metode tutup Socket, thread yang diblokir akan memunculkan SocketException.
2) I/O asinkron diimplementasikan menggunakan Selector
Jika thread diblokir di Selector.select (di java.nio.channels), memanggil metode bangun akan menyebabkan pengecualian ClosedSelectorException.
3) Akuisisi kunci
Jika thread sedang menunggu untuk mendapatkan kunci internal, kami tidak dapat menghentikannya. Namun, dengan menggunakan metode lockInterruptible dari kelas Lock, kami dapat memberikan kemampuan interupsi sambil menunggu kuncian.
Selain itu, dalam kerangka kerja yang memisahkan tugas dan thread, tugas biasanya tidak mengetahui thread mana yang akan dipanggil, dan karena itu tidak mengetahui strategi thread pemanggil untuk menangani interupsi. Oleh karena itu, setelah tugas menetapkan tanda gangguan thread, tidak ada jaminan bahwa tugas tersebut akan dibatalkan. Oleh karena itu, ada dua prinsip pemrograman:
1) Anda tidak boleh menginterupsi thread kecuali Anda mengetahui kebijakan interupsinya.
Prinsip ini memberi tahu kita bahwa kita tidak boleh memanggil metode interupsi thread secara langsung dalam kerangka kerja seperti Executer, tetapi harus menggunakan metode seperti Future.cancel untuk membatalkan tugas.
2) Kode tugas tidak boleh menebak apa arti interupsi pada thread eksekusi.
Prinsip ini memberi tahu kita bahwa ketika kode umum menemukan InterruptedException, kode tersebut tidak boleh menangkapnya dan "menelannya", tetapi harus terus membuangnya ke kode atas.
Singkatnya, mekanisme interupsi non-preemptif di Java mengharuskan kita untuk mengubah ide interupsi preemptif tradisional dan mengadopsi prinsip dan pola pemrograman yang sesuai berdasarkan pemahaman esensinya.