Executor arayüzüne neden ihtiyacınız var?

Java 5'ten önce, uygulamanızda tüm kendi kod iş parçacığı yönetiminizi yazmanız gerekiyordu. Ek olarak, biryeni Konunesne, kaynak yoğun bir işlemdir ve her hafif görev için yeni bir iş parçacığı oluşturmak mantıklı değildir. Ve bu sorun, kesinlikle çok iş parçacıklı uygulamaların her geliştiricisine aşina olduğu için, bu işlevi Java'ya Executor çerçevesi olarak getirmeye karar verdiler.

Büyük fikir nedir? Çok basit: her yeni görev için yeni bir iş parçacığı oluşturmak yerine, iş parçacıkları bir tür "depoda" tutulur ve yeni bir görev geldiğinde, yeni bir iş parçacığı oluşturmak yerine mevcut bir iş parçacığını alırız.

Bu çerçevenin ana arabirimleri, her biri bir öncekinin işlevselliğini artıran Executor , ExecutorService ve ScheduledExecutorService'dir .

Yürütücü arabirimi, temel arabirimdir. Bir Runnable nesnesi tarafından uygulanan tek bir geçersiz yürütme (Runnable komutu) yöntemi bildirir .

ExecutorService arayüzü daha ilgi çekicidir. İşin tamamlanmasını yönetme yöntemlerinin yanı sıra bir tür sonuç döndürme yöntemleri vardır. Yöntemlerine daha yakından bakalım:

Yöntem Tanım
geçersiz kapatma(); Bu yöntemin çağrılması ExecutorService'i durdurur . Halihazırda işlenmek üzere gönderilmiş olan tüm görevler tamamlanacak ancak yeni görevler kabul edilmeyecektir.
List<Runnable> shutdownNow();

Bu yöntemin çağrılması ExecutorService'i durdurur . Thread.interrupt, halihazırda işlenmek üzere gönderilmiş olan tüm görevler için çağrılacaktır. Bu yöntem, kuyruğa alınmış görevlerin bir listesini döndürür.

Yöntem, çağrıldığı anda "devam eden" tüm görevlerin tamamlanmasını beklemez.

Uyarı: Bu yöntemi çağırmak kaynakları sızdırabilir.

boole isShutdown(); ExecutorService'in durdurulup durdurulmadığını kontrol eder .
boole isTerminal(); ExecutorService kapatıldıktan sonra tüm görevler tamamlandıysa true değerini döndürür . shutdown() veya shutdownNow() çağrılana kadar her zaman false döndürür .
boolean waitTermination(uzun zaman aşımı, TimeUnit birimi) InterruptedException hatası verir;

shutdown() yöntemi çağrıldıktan sonra , bu yöntem çağrıldığı iş parçacığını aşağıdaki koşullardan biri doğru olana kadar engeller:

  • tüm zamanlanmış görevler tamamlandı;
  • yönteme geçirilen zaman aşımı süresi doldu;
  • mevcut iş parçacığı kesilir.

Tüm görevler tamamlanırsa true , sonlandırmadan önce zaman aşımı süresi geçerse false döndürür .

<T> Gelecek<T> gönder(Çağrılabilir<T> görevi);

ExecutorService'e Çağrılabilir bir görev ekler ve Future arabirimini uygulayan bir nesne döndürür .

<T> , geçirilen görevin sonucunun türüdür.

<T> Gelecek<T> gönder(Çalıştırılabilir görev, T sonucu);

ExecutorService'e Runnable görevi ekler ve Future arabirimini uygulayan bir nesne döndürür .

T sonuç parametresi , elde edilen sonuç üzerinde get() yöntemine yapılan bir çağrıyla döndürülen şeydir.Gelecek nesne.

gelecek<?> gönder(Çalıştırılabilir görev);

ExecutorService'e Runnable görevi ekler ve Future arabirimini uygulayan bir nesne döndürür .

Ortaya çıkan Future nesnesi üzerinde get() yöntemini çağırırsak null elde ederiz.

<T> List<Future<T>> invokeAll(Collection<?, Callable<T>> görevlerini genişletir) InterruptedException'ı atar;

Çağrılabilir görevlerin bir listesini ExecutorService öğesine iletir . İşin sonucunu alabileceğimiz Vadeli İşlemlerin bir listesini döndürür. Bu liste, gönderilen tüm görevler tamamlandığında döndürülür.

Yöntem çalışırken görevler koleksiyonu değiştirilirse, bu yöntemin sonucu tanımsızdır.

<T> List<Future<T>> invokeAll(Collection<?, Callable<T>> görevlerini genişletir, uzun zaman aşımı, TimeUnit birimi) InterruptedException'ı atar;

Çağrılabilir görevlerin bir listesini ExecutorService öğesine iletir . Çalışmanın sonucunu alabileceğimiz Vadeli İşlemlerin bir listesini döndürür. Bu liste, hangisi önce gelirse, tüm geçirilen görevler tamamlandığında veya yönteme geçirilen zaman aşımı süresi geçtikten sonra döndürülür.

Zaman aşımı süresi geçerse, tamamlanmamış görevler iptal edilir.

Not: İptal edilen bir görevin çalışmayı durdurmaması mümkündür (bu yan etkiyi örnekte göreceğiz).

Yöntem çalışırken görevler koleksiyonu değiştirilirse, bu yöntemin sonucu tanımsızdır.

<T> T invokeAny(Collection<?, Callable<T>> görevlerini genişletir) InterruptedException, ExecutionException atar;

Çağrılabilir görevlerin bir listesini ExecutorService öğesine iletir . Bir istisna (varsa) atmadan tamamlanan görevlerden birinin (varsa) sonucunu döndürür.

Yöntem çalışırken görevler koleksiyonu değiştirilirse, bu yöntemin sonucu tanımsızdır.

<T> T invokeAny(Collection<?, Callable<T>> görevlerini genişletir, uzun zaman aşımı, TimeUnit birimi) InterruptedException, ExecutionException, TimeoutException atar;

Çağrılabilir görevlerin bir listesini ExecutorService öğesine iletir . Yönteme geçirilen zaman aşımı süresi dolmadan önce bir özel durum oluşturmadan tamamlanan görevlerden birinin (varsa) sonucunu döndürür.

Yöntem çalışırken görevler koleksiyonu değiştirilirse, bu yöntemin sonucu tanımsızdır.

ExecutorService ile çalışmanın küçük bir örneğine bakalım .


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

Çıktı:

done
done
Futures alındı
​​uyku 1: uyku yarıda kesildi
uyku 1: uyku yarıda kesildi
yapıldı
yapıldı
doğru
doğru

Her görev 5 saniye boyunca çalışır. İki iş parçacığı için bir havuz oluşturduk, bu nedenle ilk iki çıktı satırı çok mantıklı.

Program başladıktan altı saniye sonra invokeAll yöntemi zaman aşımına uğrar ve sonuç Futures listesi olarak döndürülür . Bu, Futures Received çıktı dizisinden görülebilir .

İlk iki görev tamamlandıktan sonra iki görev daha başlar. Ancak invokeAll yönteminde ayarlanan zaman aşımı süresi dolduğundan , bu iki görevin tamamlanması için zaman yoktur. Bir "iptal" komutu alırlar . Bu nedenle çıktıda uyku 1: uyku kesintili iki satır bulunur .

Ve sonra done ile iki satır daha görebilirsiniz . Bu, invokeAll yöntemini açıklarken bahsettiğim yan etkidir .

Beşinci ve son görev hiç başlamaz, dolayısıyla çıktıda bununla ilgili hiçbir şey görmeyiz.

Son iki satır, isShutdown ve isTerished yöntemlerinin çağrılmasının sonucudur .

Bu örneği hata ayıklama modunda çalıştırmak ve zaman aşımı süresi geçtikten sonra görev durumuna bakmak da ilginçtir ( executorService.shutdown(); ile satırda bir kesme noktası ayarlayın ):

İki görevin normal olarak Tamamlandığını ve üç görevin "İptal Edildiğini" görüyoruz .

ScheduledExecutorService

Yürütücülerle ilgili tartışmamızı sonlandırmak için, ScheduledExecutorService'e bir göz atalım .

4 yöntemi vardır:

Yöntem Tanım
public ScheduledFuture<?> program(Çalıştırılabilir komut, uzun gecikme, TimeUnit birimi); Geçirilen Runnable görevini, bağımsız değişken olarak belirtilen gecikmeden sonra çalışacak şekilde zamanlar.
public <V> ScheduledFuture<V> program(Callable<V> callable, long delay, TimeUnit unit); Geçirilen Callable görevini, bağımsız değişken olarak belirtilen gecikmeden sonra çalışacak şekilde zamanlar.
public ScheduledFuture<?> ScheduleAtFixedRate(Çalıştırılabilir komut, uzun ilkGecikme, uzun süre, ZamanBirimi birimi); Geçilen görevin, ilk kez InitialDelay'den sonra yürütülecek ve sonraki her çalıştırma, period ' dan sonra başlayacak şekilde periyodik olarak yürütülmesini zamanlar .
public ScheduledFuture<?> programWithFixedDelay(Çalıştırılabilir komut, uzun ilkGecikme, uzun gecikme, ZamanBirimi birimi); Geçilen görevin, InitialDelay'den sonra ilk kez yürütülecek olan periyodik olarak yürütülmesini zamanlar ve sonraki her çalıştırma gecikmeden sonra başlar (önceki çalıştırmanın tamamlanması ile mevcut çalıştırmanın başlaması arasındaki süre).