Timer adalah API awal di JDK. Kami biasanya menggunakan timer dan timertask untuk melakukan tugas dengan penundaan dan periodisitas sebelum newscheduledthreadpool keluar, tetapi timer memiliki beberapa kekurangan. Mengapa kita mengatakannya?
Timer hanya membuat utas unik untuk melakukan semua tugas pengatur waktu. Jika pelaksanaan tugas timer memakan waktu, itu akan menyebabkan masalah dengan keakuratan timertasks lainnya. Misalnya, satu timertask dieksekusi setiap 10 detik, dan timertask lain dieksekusi setiap 40 ms. Tugas yang diulang akan disebut 4 kali berturut -turut setelah tugas -tugas berikutnya selesai, atau 4 panggilan benar -benar "hilang". Masalah lain dengan pengatur waktu adalah bahwa jika Timertask melempar pengecualian yang tidak dicentang, itu akan mengakhiri utas timer. Dalam hal ini, timer tidak akan membalas eksekusi utas lagi; Secara keliru percaya bahwa seluruh timer telah dibatalkan. Timertask, yang telah dijadwalkan tetapi belum dieksekusi, tidak akan pernah dieksekusi lagi, dan tugas -tugas baru tidak dapat dijadwalkan.
Di sini saya membuat demo kecil untuk mereproduksi masalah, kodenya adalah sebagai berikut:
Paket com.hjc; import java.util.timer; import java.util.timertask;/*** dibuat oleh Cong pada 2018/7/12. */TimerTest kelas publik {// Buat timer timer timer timer = timer baru (); public static void main (string [] args) {// Tambahkan tugas 1, tunda eksekusi timer.schedule (timerKask baru () {@Override public void run () {System.out.println ("-satu tugas ---"); coba {thread. RunimeException ("error");}}, 500); // Tambahkan tugas 2, tunda eksekusi timer.schedule (new timertask () {@Override public void run () {for (;;) {System.out.println ("-Two Task ---"); coba {thread.sleep (1000);} Catch (interrupted exception e) {// too-too-gener (1000); }}, 1000); }}Seperti disebutkan di atas, tugas pertama kali ditambahkan untuk dieksekusi setelah 500ms, dan kemudian tugas kedua ditambahkan untuk dieksekusi setelah 1s. Yang kami harapkan adalah bahwa ketika tugas pertama menghasilkan --- satu tugas --- dan menunggu 1s, tugas kedua akan menghasilkan-dua tugas ---,
Namun, setelah menjalankan kode, outputnya adalah sebagai berikut:
Contoh 2,
Shedule kelas publik {Private Static Long Start; public static void main (string [] args) {timerKask Task = new timertask () {public void run () {System.out.println (System.CurrentTimeMillis ()-start); coba {thread.sleep (3000); } catch (InterruptedException e) {E.PrintStackTrace (); }}}; TimerTask Task1 = new timertask () {@Override public void run () {System.out.println (System.CurrentTimeMillis ()-start); }}; Timer timer = timer baru (); start = system.currentTimeMillis (); // Mulai tugas yang dijadwalkan, jalankan timer.schedule (Tugas, 1000); // Mulai tugas yang dijadwalkan, jalankan timer.schedule (Task1.3000); }}Kami berharap bahwa program di atas akan dieksekusi setelah tugas pertama dieksekusi setelah tugas kedua adalah 3s, yaitu, output satu 1000 dan satu 3000.
Hasil operasi yang sebenarnya adalah sebagai berikut:
Hasil operasi yang sebenarnya tidak seperti yang kita inginkan. Hasil dunia adalah bahwa tugas kedua adalah output setelah 4s, yaitu, 4001 adalah sekitar 4 detik. Kemana perginya sebagian waktu itu? Waktu itu ditempati oleh tidur dari tugas pertama kami.
Sekarang kami menghapus thread.sleep () dalam tugas pertama; Apakah baris kode ini berjalan dengan benar? Hasil operasi adalah sebagai berikut:
Dapat dilihat bahwa tugas pertama dieksekusi setelah 1s, dan tugas kedua dieksekusi setelah 3S setelah tugas pertama dieksekusi.
Ini berarti bahwa timer hanya membuat utas unik untuk melakukan semua tugas pengatur waktu. Jika pelaksanaan tugas timer memakan waktu, itu akan menyebabkan masalah dengan keakuratan timertasks lainnya.
Analisis Prinsip Implementasi Pengatur Waktu
Berikut ini adalah pengantar singkat tentang prinsip timer. Gambar berikut adalah pengantar model prinsip timer:
1.TaskQueue adalah antrian prioritas yang diimplementasikan oleh tumpukan pohon biner yang seimbang, dan setiap objek timer memiliki antrian tugas yang unik di dalamnya. Metode jadwal timer panggilan utas pengguna adalah untuk menambahkan tugas timertask ke antrian taskqueue. Saat memanggil metode jadwal, parameter penundaan lama digunakan untuk menunjukkan berapa lama tugas ditunda untuk dieksekusi.
2. TimerThread adalah utas yang melakukan tugas tertentu. Ini memperoleh tugas dengan prioritas paling sedikit dari antrian taskqueue untuk dieksekusi. Perlu dicatat bahwa hanya setelah tugas saat ini dieksekusi, tugas berikutnya akan diperoleh dari antrian. Terlepas dari apakah ada waktu tunda yang ditetapkan dalam antrian, timer hanya memiliki satu utas timerThread, sehingga dapat dilihat bahwa implementasi internal timer adalah model konsumen tunggal multi-produser.
Dari model implementasi, kita dapat mengetahui bahwa untuk mengeksplorasi masalah di atas, Anda hanya perlu melihat implementasi timerThread. Kode sumber logis utama dari metode run timerthread adalah sebagai berikut:
public void run () {coba {mainloop (); } akhirnya {// seseorang membunuh utas ini, bertindak seolah -olah timer telah membatalkan sinkronisasi (antrian) {newtasksmaybescheduled = false; antrian.clear (); // Hilangkan referensi yang sudah ketinggalan zaman}}} private void mainloop () {while (true) {try {timerKask tugas; Boolean Taskfired; // kunci disinkronkan (antrian) {......} if (TaskFired) Task.run (); // Execute Task} catch (InterruptedException e) {}}}Dapat dilihat bahwa ketika pengecualian selain interupsi Exception dilemparkan selama pelaksanaan tugas, satu -satunya utas konsumen akan berakhir karena melempar pengecualian, dan tugas -tugas lain yang akan dieksekusi dalam antrian akan dihapus. Oleh karena itu, yang terbaik adalah menggunakan struktur mencoba-tangkapan untuk menangkap pengecualian utama dalam metode run timertask, dan tidak melempar pengecualian di luar metode run.
Bahkan, untuk mengimplementasikan fungsi yang mirip dengan timer, ini adalah pilihan yang lebih baik untuk menggunakan jadwal JadwalTHreadPoolExecutor. Salah satu tugas dalam JadwalTHeadPoolExecutor melempar pengecualian, dan tugas -tugas lainnya tidak terpengaruh.
Contoh JadwalTHreadPoolExecutor adalah sebagai berikut:
/*** Dibuat oleh Cong pada 2018/7/12. */kelas publik JadwalTHreadPoolExecutortestest {Static TerjadwalTHreadPoolExecutor Dit jadwalThreadPoolExecutor = new JadwalThreadPoolExecutor (1); public static void main (string [] args) {scheduledthreadpoolexecutor.schedule (runnable baru () {public void run () {System.out.println ("-satu tugas ---"); coba {thread.sleep (1000);} catch (interruptEcception e) {e. e.sceptscept (1000);} new trowrace (interruptEcception e) {E. e.seprintstack (1000); }}, 500, timeunit.microseconds); JadwalThreadPoolExecutor.schedule (runnable baru () {public void run () {for (int i = 0; i <5; ++ i) {System.out.println ("-Two Task ---"); coba {thread.sleep (1000);} Catch (interruptException e) {e.print {e.crinte (1000);} catch (interruptException e) {e.print {e.print {E.) {E.) {E.) {E.) {E.) {E.) {E.) {E.) {E.); Timeunit.microseconds); JadwalThreadPoolExecutor.shutdown (); }}Hasil operasi adalah sebagai berikut:
Alasan mengapa tugas -tugas lain dari JadwalThreadPoolExecutor tidak terpengaruh oleh tugas yang melempar pengecualian adalah karena tangkapan menjatuhkan pengecualian dalam tugas yang dijadwalkan untuk menggunakan tangkapan untuk menangkap pengecualian dan mencetak log dalam metode rool executor, tetapi merupakan praktik terbaik untuk menggunakan tangkapan untuk menangkap pengecualian dan mencetak log dalam metode run of the Thread Pool.