Cara Menghentikan Pelaksanaan Selepas Waktu Tertentu di Java

1. Gambaran keseluruhan

Dalam artikel ini, kita akan belajar bagaimana kita dapat mengakhiri pelaksanaan yang lama setelah waktu tertentu. Kami akan meneroka pelbagai penyelesaian untuk masalah ini. Juga, kita akan merangkumi beberapa perangkap mereka.

2. Menggunakan Gelung

Bayangkan bahawa kita memproses sejumlah item dalam satu gelung, seperti beberapa butiran item produk dalam aplikasi e-dagang, tetapi mungkin tidak perlu menyelesaikan semua item.

Sebenarnya, kami hanya mahu memproses hingga waktu tertentu, dan setelah itu, kami ingin menghentikan pelaksanaannya dan menunjukkan apa sahaja senarai yang telah diproses hingga saat itu.

Mari lihat contoh ringkas:

long start = System.currentTimeMillis(); long end = start + 30*1000; while (System.currentTimeMillis() < end) { // Some expensive operation on the item. }

Di sini, gelung akan putus jika waktunya melepasi had 30 saat. Terdapat beberapa perkara penting dalam penyelesaian di atas:

  • Ketepatan rendah: Gelung dapat berjalan lebih lama daripada had masa yang dikenakan . Ini bergantung pada masa yang diperlukan oleh setiap lelaran. Sebagai contoh, jika setiap iterasi mungkin memakan waktu hingga 7 saat, maka total waktu dapat mencapai 35 detik, yaitu sekitar 17% lebih lama dari had waktu yang diinginkan 30 saat
  • Menyekat: Pemprosesan sedemikian di utas utama mungkin bukan idea yang baik kerana akan menyekatnya dalam jangka masa yang lama . Sebaliknya, operasi ini harus dipisahkan dari utas utama

Di bahagian seterusnya, kita akan membincangkan bagaimana pendekatan berasaskan gangguan menghilangkan batasan ini.

3. Menggunakan Mekanisme Selang

Di sini, kami akan menggunakan utas yang berasingan untuk menjalankan operasi yang telah lama berjalan. Benang utama akan menghantar isyarat gangguan ke utas pekerja pada waktu tamat.

Sekiranya utas pekerja masih hidup, ia akan menangkap isyarat dan menghentikan pelaksanaannya. Sekiranya pekerja selesai sebelum tamat waktu, ia tidak akan memberi kesan pada utas pekerja.

Mari lihat urutan pekerja:

class LongRunningTask implements Runnable { @Override public void run() { try { while (!Thread.interrupted()) { Thread.sleep(500); } } catch (InterruptedException e) { // log error } } }

Di sini, Thread.sleep mensimulasikan operasi yang berjalan lama. Daripada ini, mungkin ada operasi lain. Penting untuk memeriksa bendera gangguan kerana tidak semua operasi terganggu . Oleh itu, kita mesti memeriksa bendera secara manual.

Juga, kita harus memeriksa bendera ini dalam setiap lelaran untuk memastikan bahawa utas berhenti melaksanakannya sendiri dalam kelewatan satu lelaran paling banyak.

Seterusnya, kami akan merangkumi tiga mekanisme yang berbeza untuk menghantar isyarat gangguan.

3.1. Menggunakan Pemasa

Sebagai alternatif, kita boleh membuat TimerTask untuk mengganggu urutan pekerja semasa tamat masa:

class TimeOutTask extends TimerTask { private Thread t; private Timer timer; TimeOutTask(Thread t, Timer timer){ this.t = t; this.timer = timer; } public void run() { if (t != null && t.isAlive()) { t.interrupt(); timer.cancel(); } } }

Di sini, kami telah menentukan TimerTask yang mengambil utas pekerja pada saat penciptaannya. Ini akan mengganggu urutan pekerja apabila menggunakan kaedah menjalankannya . The Timer akan mencetuskan TimerTask selepas kelewatan yang dinyatakan:

Thread t = new Thread(new LongRunningTask()); Timer timer = new Timer(); timer.schedule(new TimeOutTask(t, timer), 30*1000); t.start();

3.2. Menggunakan Kaedah Masa Depan # dapatkan

Kami juga dapat menggunakan kaedah get of Future dan bukannya menggunakan Timer :

ExecutorService executor = Executors.newSingleThreadExecutor(); Future future = executor.submit(new LongRunningTask()); try { f.get(30, TimeUnit.SECONDS); } catch (TimeoutException e) { f.cancel(true); } finally { service.shutdownNow(); }

Di sini, kami menggunakan ExecutorService mengemukakan thread pekerja pulangan satu contoh Future , yang mendapatkan kaedah akan menyekat thread utama sehingga masa yang ditetapkan. Ia akan menaikkan TimeoutException selepas tamat masa yang ditentukan. Di blok tangkapan , kami mengganggu urutan pekerja dengan memanggil kaedah batal pada objek F uture .

Manfaat utama pendekatan ini berbanding yang sebelumnya adalah menggunakan kolam untuk menguruskan utas, sementara Pemasa hanya menggunakan satu utas (tanpa kumpulan) .

3.3. Menggunakan Perkhidmatan BerjadualExcecutorSercvice

Kami juga boleh menggunakan Perkhidmatan BerjadualExecutor untuk mengganggu tugas. Kelas ini adalah lanjutan dari ExecutorService dan menyediakan fungsi yang sama dengan penambahan beberapa kaedah yang berkaitan dengan penjadualan pelaksanaan. Ini dapat melaksanakan tugas yang diberikan setelah penundaan tertentu dari unit waktu yang ditetapkan:

ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); Future future = executor.submit(new LongRunningTask()); executor.schedule(new Runnable(){ public void run(){ future.cancel(true); } }, 1000, TimeUnit.MILLISECONDS); executor.shutdown();

Di sini, kami membuat kumpulan utas berjadual bersaiz dua dengan kaedah newSchedchedThreadPool . The ScheduledExecutorService # jadual kaedah mengambil Runnable , nilai kelewatan, dan unit kelewatan.

Program di atas menjadualkan tugas yang akan dilaksanakan setelah satu saat dari saat penyerahan. Tugas ini akan membatalkan tugas jangka panjang yang asal.

Perhatikan bahawa tidak seperti pendekatan sebelumnya, kami tidak menyekat utas utama dengan memanggil kaedah Future # get . Oleh itu, ia adalah pendekatan yang paling disukai di antara semua pendekatan yang disebutkan di atas .

4. Adakah Terdapat Jaminan?

Tidak ada jaminan bahawa pelaksanaan dihentikan setelah waktu tertentu . Sebab utama adalah bahawa tidak semua kaedah penyekat dapat terganggu. Sebenarnya, hanya ada beberapa kaedah yang dapat diinterupsi. Oleh itu, jika utas terganggu dan bendera ditetapkan, tidak ada yang lain akan berlaku sehingga mencapai salah satu kaedah yang boleh dihentikan ini .

Contohnya, kaedah membaca dan menulis hanya boleh terganggu jika digunakan pada aliran yang dibuat dengan InterruptibleChannel . BufferedReader bukan InterruptibleChannel . Jadi, jika utas menggunakannya untuk membaca fail, panggilan mengganggu () pada utas ini disekat dalam kaedah baca tidak memberi kesan.

Namun, kami dapat secara jelas memeriksa bendera gangguan setelah setiap kali dibaca dalam satu gelung. Ini akan memberi jaminan yang munasabah untuk menghentikan tali dengan sedikit kelewatan. Tetapi, ini tidak menjamin untuk menghentikan utas setelah waktu yang ketat, kerana kita tidak tahu berapa banyak masa yang diperlukan untuk operasi membaca.

Sebaliknya, kaedah menunggu dari kelas Objek tidak boleh terganggu. Oleh itu, utas yang disekat dalam kaedah tunggu akan segera melancarkan InterruptException setelah bendera interrupt ditetapkan.

Kita dapat mengenal pasti kaedah menyekat dengan mencari lontaran InterruptException dalam tandatangan kaedah mereka.

Satu nasihat penting adalah mengelakkan penggunaan kaedah Thread.stop () yang tidak digunakan lagi. Menghentikan utas menyebabkan ia membuka kunci semua monitor yang terkunci. Ini berlaku kerana pengecualian ThreadDeath yang menyebarkan tumpukan.

Sekiranya mana-mana objek yang sebelumnya dilindungi oleh monitor ini berada dalam keadaan tidak konsisten, objek yang tidak konsisten akan dapat dilihat oleh utas lain. Ini boleh menyebabkan tingkah laku sewenang-wenang yang sangat sukar untuk dikesan dan difikirkan.

5. Kesimpulan

Dalam tutorial ini, kami telah mempelajari pelbagai teknik untuk menghentikan pelaksanaan setelah waktu tertentu, bersama dengan kebaikan dan keburukan masing-masing. Kod sumber lengkap boleh didapati di GitHub.