Mengapa anda memerlukan antara muka Pelaksana?

Sebelum Java 5, anda perlu menulis semua pengurusan benang kod anda sendiri dalam aplikasi anda. Selain itu, mewujudkan aBenang baruobjek ialah operasi intensif sumber, dan tidak masuk akal untuk mencipta benang baharu untuk setiap tugas yang ringan. Dan kerana masalah ini sudah biasa kepada setiap pembangun aplikasi berbilang benang, mereka memutuskan untuk membawa fungsi ini ke dalam Java sebagai rangka kerja Pelaksana .

Apakah idea besarnya? Ianya mudah: daripada mencipta utas baharu untuk setiap tugasan baharu, utas disimpan dalam sejenis "storan", dan apabila tugasan baharu tiba, kami mendapatkan semula utas sedia ada dan bukannya membuat yang baharu.

Antara muka utama rangka kerja ini ialah Executor , ExecutorService dan ScheduledExecutorService , setiap satunya memanjangkan fungsi yang sebelumnya.

Antara muka Pelaksana ialah antara muka asas. Ia mengisytiharkan kaedah laksanakan kekosongan tunggal (Arahan Boleh Dijalankan) yang dilaksanakan oleh objek Boleh Dijalankan .

Antara muka ExecutorService lebih menarik. Ia mempunyai kaedah untuk menguruskan penyiapan kerja, serta kaedah untuk mengembalikan beberapa jenis hasil. Mari kita lihat lebih dekat kaedahnya:

Kaedah Penerangan
void shutdown(); Memanggil kaedah ini menghentikan ExecutorService . Semua tugasan yang telah diserahkan untuk diproses akan diselesaikan, tetapi tugasan baharu tidak akan diterima.
List<Runnable> shutdownNow();

Memanggil kaedah ini menghentikan ExecutorService . Thread.interrupt akan dipanggil untuk semua tugasan yang telah diserahkan untuk diproses. Kaedah ini mengembalikan senarai tugas beratur.

Kaedah ini tidak menunggu untuk menyelesaikan semua tugasan yang sedang "dalam proses" pada masa kaedah dipanggil.

Amaran: Memanggil kaedah ini mungkin membocorkan sumber.

boolean isShutdown(); Menyemak sama ada ExecutorService dihentikan.
boolean isTerminated(); Mengembalikan benar jika semua tugas telah selesai selepas penutupan ExecutorService . Sehingga shutdown() atau shutdownNow() dipanggil, ia akan sentiasa mengembalikan false .
boolean awaitTermination(lama tamat masa, unit TimeUnit) membuang InterruptedException;

Selepas kaedah shutdown() dipanggil, kaedah ini menyekat benang yang dipanggil, sehingga salah satu daripada syarat berikut adalah benar:

  • semua tugas yang dijadualkan selesai;
  • tamat masa yang diluluskan kepada kaedah telah berlalu;
  • benang semasa terganggu.

Mengembalikan benar jika semua tugasan selesai, dan palsu jika tamat masa berlalu sebelum penamatan.

<T> Masa hadapan<T> serahkan (Tugas<T> Boleh Dipanggil);

Menambah tugas Boleh Panggil pada ExecutorService dan mengembalikan objek yang melaksanakan antara muka Masa Depan .

<T> ialah jenis hasil tugasan yang diluluskan.

<T> Masa Depan<T> serahkan(Tugas boleh dijalankan, hasil T);

Menambahkan tugas Runnable pada ExecutorService dan mengembalikan objek yang melaksanakan antara muka Masa Depan .

Parameter hasil T ialah apa yang dikembalikan melalui panggilan ke kaedah get() pada yang terhasilObjek masa depan.

Future<?> submit(Tugas boleh dijalankan);

Menambahkan tugas Runnable pada ExecutorService dan mengembalikan objek yang melaksanakan antara muka Masa Depan .

Jika kita memanggil kaedah get() pada objek Masa Depan yang terhasil , maka kita mendapat null.

<T> List<Future<T>> invokeAll(Collection<? melanjutkan tugasan Callable<T>>) melontar InterruptedException;

Melewati senarai tugas Boleh Panggil kepada ExecutorService . Mengembalikan senarai Niaga Hadapan yang mana kita boleh mendapatkan hasil kerja. Senarai ini dikembalikan apabila semua tugasan yang diserahkan telah selesai.

Jika pengumpulan tugasan diubah suai semasa kaedah berjalan, hasil kaedah ini tidak ditentukan.

<T> List<Future<T>> invokeAll(Collection<? melanjutkan tugasan Callable<T>>, tamat masa yang lama, unit TimeUnit) membuang InterruptedException;

Melewati senarai tugas Boleh Panggil kepada ExecutorService . Mengembalikan senarai Niaga Hadapan yang mana kita boleh mendapatkan hasil kerja. Senarai ini dikembalikan apabila semua tugasan yang diluluskan selesai, atau selepas tamat masa berlalu kepada kaedah telah berlalu, yang mana lebih dahulu.

Jika tamat masa berlalu, tugasan yang belum selesai akan dibatalkan.

Nota: Ada kemungkinan bahawa tugas yang dibatalkan tidak akan berhenti berjalan (kita akan melihat kesan sampingan ini dalam contoh).

Jika pengumpulan tugasan diubah suai semasa kaedah berjalan, hasil kaedah ini tidak ditentukan.

<T> T invokeAny(Collection<? melanjutkan tugasan Callable<T>>) melontar InterruptedException, ExecutionException;

Melewati senarai tugas Boleh Panggil kepada ExecutorService . Mengembalikan hasil salah satu tugas (jika ada) yang diselesaikan tanpa membuang pengecualian (jika ada).

Jika pengumpulan tugasan diubah suai semasa kaedah berjalan, hasil kaedah ini tidak ditentukan.

<T> T invokeAny(Collection<? melanjutkan tugasan Callable<T>>, tamat masa yang lama, unit TimeUnit) melontar InterruptedException, ExecutionException, TimeoutException;

Melewati senarai tugas Boleh Panggil kepada ExecutorService . Mengembalikan hasil salah satu tugas (jika ada) yang diselesaikan tanpa membuang pengecualian sebelum tamat masa berlalu kepada kaedah telah berlalu.

Jika pengumpulan tugasan diubah suai semasa kaedah berjalan, hasil kaedah ini tidak ditentukan.

Mari 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;
       }
   }
}

Pengeluaran:

selesai
dilakukan
Niaga hadapan menerima
tidur 1: tidur terganggu
tidur 1: tidur terganggu
selesai
dilakukan
benar
benar

Setiap tugasan berjalan selama 5 saat. Kami mencipta kolam untuk dua utas, jadi dua baris output pertama masuk akal.

Enam saat selepas program bermula, kaedah invokeAll tamat masa dan hasilnya dikembalikan sebagai senarai Niaga Hadapan . Ini boleh dilihat daripada rentetan keluaran Niaga hadapan diterima .

Selepas dua tugasan pertama selesai, dua lagi bermula. Tetapi kerana tamat masa yang ditetapkan dalam kaedah invokeAll berlalu, kedua-dua tugas ini tidak mempunyai masa untuk diselesaikan. Mereka menerima arahan "batal" . Itulah sebabnya output mempunyai dua baris dengan sleep 1: sleep interrupted .

Dan kemudian anda boleh melihat dua baris lagi dengan selesai . Ini adalah kesan sampingan yang saya nyatakan semasa menerangkan kaedah invokeAll .

Tugasan kelima dan terakhir tidak pernah dimulakan, jadi kami tidak melihat apa-apa tentangnya dalam output.

Dua baris terakhir adalah hasil daripada memanggil kaedah isShutdown dan isTerminated .

Ia juga menarik untuk menjalankan contoh ini dalam mod nyahpepijat dan melihat status tugas selepas tamat masa berlalu (tetapkan titik putus pada baris dengan executorService.shutdown(); ):

Kami melihat bahawa dua tugasan Selesai seperti biasa , dan tiga tugasan telah "Dibatalkan" .

ScheduledExecutorService

Untuk mengakhiri perbincangan kita tentang pelaksana, mari kita lihat pada ScheduledExecutorService .

Ia mempunyai 4 kaedah:

Kaedah Penerangan
jadual ScheduledFuture<?> awam(Arahan boleh dijalankan, kelewatan lama, unit TimeUnit); Menjadualkan tugas Runnable yang diluluskan untuk dijalankan sekali selepas kelewatan yang dinyatakan sebagai hujah.
jadual <V> ScheduledFuture<V> awam (Boleh dipanggil<V> boleh dipanggil, kelewatan lama, unit TimeUnit); Menjadualkan tugas Boleh Panggil yang diluluskan untuk dijalankan sekali selepas kelewatan yang dinyatakan sebagai hujah.
awam ScheduledFuture<?> scheduleAtFixedRate(Arahan boleh dijalankan, long initialDelay, tempoh panjang, unit TimeUnit); Jadualkan pelaksanaan berkala bagi tugas yang diluluskan, yang akan dilaksanakan buat kali pertama selepas initialDelay , dan setiap larian berikutnya akan bermula selepas tempoh .
public ScheduledFuture<?> scheduleWithFixedDelay(Arahan boleh dijalankan, Lengah permulaan panjang, kelewatan lama, unit TimeUnit); Jadualkan pelaksanaan berkala bagi tugasan yang diluluskan, yang akan dilaksanakan buat kali pertama selepas initialDelay , dan setiap larian berikutnya akan bermula selepas kelewatan (tempoh antara penyiapan larian sebelumnya dan permulaan larian semasa).