Mengapa Anda memerlukan antarmuka Pelaksana?

Sebelum Java 5, Anda harus menulis semua manajemen utas kode Anda sendiri di aplikasi Anda. Selain itu, membuat aUtas baruobjek adalah operasi intensif sumber daya, dan tidak masuk akal untuk membuat utas baru untuk setiap tugas ringan. Dan karena masalah ini sudah tidak asing lagi bagi setiap pengembang aplikasi multi-utas, mereka memutuskan untuk membawa fungsionalitas ini ke dalam Java sebagai kerangka kerja Pelaksana .

Apa ide besarnya? Sederhana: alih-alih membuat utas baru untuk setiap tugas baru, utas disimpan dalam semacam "penyimpanan", dan ketika tugas baru tiba, kami mengambil utas yang ada alih-alih membuat yang baru.

Antarmuka utama kerangka kerja ini adalah Executor , ExecutorService dan ScheduledExecutorService , yang masing-masing memperluas fungsionalitas yang sebelumnya.

Antarmuka Pelaksana adalah antarmuka dasar. Ini mendeklarasikan metode eksekusi tunggal (perintah Runnable) yang diimplementasikan oleh objek Runnable .

Antarmuka ExecutorService lebih menarik. Ini memiliki metode untuk mengelola penyelesaian pekerjaan, serta metode untuk mengembalikan semacam hasil. Mari kita lihat lebih dekat metodenya:

metode Keterangan
membatalkan shutdown(); Memanggil metode ini menghentikan ExecutorService . Semua tugas yang telah dikirimkan untuk diproses akan diselesaikan, tetapi tugas baru tidak akan diterima.
Daftar<Dapat dijalankan> shutdownNow();

Memanggil metode ini menghentikan ExecutorService . Thread.interrupt akan dipanggil untuk semua tugas yang telah dikirimkan untuk diproses. Metode ini mengembalikan daftar tugas yang antri.

Metode tidak menunggu selesainya semua tugas yang "sedang berlangsung" pada saat metode dipanggil.

Peringatan: Memanggil metode ini dapat membocorkan sumber daya.

boolean isShutdown(); Memeriksa apakah ExecutorService dihentikan.
boolean dihentikan(); Mengembalikan nilai true jika semua tugas diselesaikan setelah ExecutorService dimatikan . Sampai shutdown() atau shutdownNow() dipanggil, itu akan selalu mengembalikan false .
boolean awaitTermination(waktu tunggu lama, unit TimeUnit) melempar InterruptedException;

Setelah metode shutdown() dipanggil, metode ini memblokir utas yang dipanggil, hingga salah satu dari kondisi berikut ini benar:

  • semua tugas terjadwal selesai;
  • batas waktu yang diteruskan ke metode telah berlalu;
  • utas saat ini terputus.

Mengembalikan nilai benar jika semua tugas selesai, dan salah jika batas waktu berlalu sebelum penghentian.

<T> Future<T> submit(Callable<T> task);

Menambahkan tugas Callable ke ExecutorService dan mengembalikan objek yang mengimplementasikan antarmuka Future .

<T> adalah jenis hasil dari tugas yang diteruskan.

<T> Future<T> kirimkan(Tugas yang dapat dijalankan, hasil T);

Menambahkan tugas Runnable ke ExecutorService dan mengembalikan objek yang mengimplementasikan antarmuka Masa Depan .

Parameter hasil T adalah apa yang dikembalikan oleh panggilan ke metode get() pada hasilObjek masa depan.

Future<?> submit(Tugas yang dapat dijalankan);

Menambahkan tugas Runnable ke ExecutorService dan mengembalikan objek yang mengimplementasikan antarmuka Masa Depan .

Jika kita memanggil metode get() pada objek Future yang dihasilkan , maka kita mendapatkan null.

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tugas) melempar InterruptedException;

Meneruskan daftar tugas yang Dapat Dipanggil ke ExecutorService . Mengembalikan daftar Futures dari mana kita bisa mendapatkan hasil pekerjaan. Daftar ini dikembalikan ketika semua tugas yang dikirimkan selesai.

Jika kumpulan tugas diubah saat metode sedang berjalan, hasil dari metode ini tidak ditentukan.

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tugas, timeout lama, unit TimeUnit) melempar InterruptedException;

Meneruskan daftar tugas yang Dapat Dipanggil ke ExecutorService . Mengembalikan daftar Futures dari mana kita bisa mendapatkan hasil pekerjaan. Daftar ini dikembalikan ketika semua tugas yang diteruskan selesai, atau setelah batas waktu yang diteruskan ke metode telah berlalu, mana saja yang lebih dulu.

Jika waktu habis, tugas yang belum selesai dibatalkan.

Catatan: Ada kemungkinan bahwa tugas yang dibatalkan tidak akan berhenti berjalan (kita akan melihat efek samping ini pada contoh).

Jika kumpulan tugas diubah saat metode sedang berjalan, hasil dari metode ini tidak ditentukan.

<T> T invokeAny(Collection<? extends Callable<T>> tugas) melempar InterruptedException, ExecutionException;

Meneruskan daftar tugas yang Dapat Dipanggil ke ExecutorService . Mengembalikan hasil dari salah satu tugas (jika ada) yang diselesaikan tanpa pengecualian (jika ada).

Jika kumpulan tugas diubah saat metode sedang berjalan, hasil dari metode ini tidak ditentukan.

<T> T invokeAny(Collection<? extends Callable<T>> tugas, timeout lama, unit TimeUnit) melempar InterruptedException, ExecutionException, TimeoutException;

Meneruskan daftar tugas yang Dapat Dipanggil ke ExecutorService . Mengembalikan hasil dari salah satu tugas (jika ada) yang diselesaikan tanpa melempar pengecualian sebelum batas waktu yang diteruskan ke metode telah berlalu.

Jika kumpulan tugas diubah saat metode sedang berjalan, hasil dari metode ini tidak ditentukan.

Mari kita lihat contoh kecil bekerja dengan ExecutorService .


import java.util.List;
import java.util.concurrent.*;

public class ExecutorServiceTest {
   public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
//Create an ExecutorService for 2 threads
       java.util.concurrent.ExecutorService executorService = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
// Create 5 tasks
       MyRunnable task1 = new MyRunnable();
       MyRunnable task2 = new MyRunnable();
       MyRunnable task3 = new MyRunnable();
       MyRunnable task4 = new MyRunnable();
       MyRunnable task5 = new MyRunnable();

       final List<MyRunnable> tasks = List.of(task1, task2, task3, task4, task5);
// Pass a list that contains the 5 tasks we created
       final List<Future<Void>> futures = executorService.invokeAll(tasks, 6, TimeUnit.SECONDS);
       System.out.println("Futures received");

// Stop the ExecutorService
       executorService.shutdown();

       try {
           TimeUnit.SECONDS.sleep(3);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }

       System.out.println(executorService.isShutdown());
       System.out.println(executorService.isTerminated());
   }

   public static class MyRunnable implements Callable<Void> {

       @Override
       public void call() {
// Add 2 delays. When the ExecutorService is stopped, we will see which delay is in progress when the attempt is made to stop execution of the task
           try {
               TimeUnit.SECONDS.sleep(3);
           } catch (InterruptedException e) {
               System.out.println("sleep 1: " + e.getMessage());
           }
           try {
               TimeUnit.SECONDS.sleep(2);
           } catch (InterruptedException e) {
               System.out.println("sleep 2: " + e.getMessage());
           }
           System.out.println("done");
           return null;
       }
   }
}

Keluaran:

selesai
selesai
Futures menerima
tidur 1: tidur terganggu
tidur 1: tidur terganggu
selesai
selesai
benar
benar

Setiap tugas berjalan selama 5 detik. Kami membuat kumpulan untuk dua utas, jadi dua baris keluaran pertama sangat masuk akal.

Enam detik setelah program dimulai, metode invokeAll habis waktu dan hasilnya dikembalikan sebagai daftar Futures . Hal ini terlihat dari output string Futures yang diterima .

Setelah dua tugas pertama selesai, dua lagi dimulai. Namun karena batas waktu yang diatur dalam metode invokeAll telah berlalu, kedua tugas ini tidak memiliki waktu untuk diselesaikan. Mereka menerima perintah "batal" . Itu sebabnya outputnya memiliki dua baris dengan sleep 1: sleep interrupt .

Dan kemudian Anda dapat melihat dua baris lagi dengan done . Ini adalah efek samping yang saya sebutkan saat menjelaskan metode invokeAll .

Tugas kelima dan terakhir bahkan tidak pernah dimulai, jadi kami tidak melihat apa pun tentangnya di keluaran.

Dua baris terakhir adalah hasil pemanggilan metode isShutdown dan isTerminated .

Menarik juga untuk menjalankan contoh ini dalam mode debug dan melihat status tugas setelah batas waktu berlalu (setel breakpoint pada baris dengan executorService.shutdown(); ):

Kami melihat bahwa dua tugas Selesai secara normal , dan tiga tugas "Dibatalkan" .

ScheduledExecutorService

Untuk menyimpulkan diskusi kita tentang pelaksana, mari kita lihat ScheduledExecutorService .

Ini memiliki 4 metode:

metode Keterangan
jadwal Public ScheduledFuture<?>(Perintah dapat dijalankan, penundaan lama, unit TimeUnit); Menjadwalkan tugas Runnable yang diteruskan untuk dijalankan sekali setelah penundaan yang ditentukan sebagai argumen.
jadwal <V> ScheduledFuture<V> publik (Dapat dipanggil<V> dapat dipanggil, penundaan lama, unit TimeUnit); Menjadwalkan tugas Callable yang diteruskan untuk dijalankan sekali setelah penundaan yang ditentukan sebagai argumen.
Public ScheduledFuture<?> scheduleAtFixedRate(Perintah yang dapat dijalankan, penundaan awal yang lama, jangka waktu yang lama, satuan TimeUnit); Menjadwalkan eksekusi periodik dari tugas yang diteruskan, yang akan dieksekusi untuk pertama kali setelah initialDelay , dan setiap proses berikutnya akan dimulai setelah periode .
Public ScheduledFuture<?> scheduleWithFixedDelay(Perintah yang dapat dijalankan, penundaan awal yang lama, penundaan yang lama, satuan TimeUnit); Menjadwalkan eksekusi berkala dari tugas yang diteruskan, yang akan dieksekusi untuk pertama kali setelah initialDelay , dan setiap proses selanjutnya akan dimulai setelah penundaan (periode antara penyelesaian proses sebelumnya dan dimulainya proses saat ini).