Neden 1 iş parçacığı için bir ExecutorService'e ihtiyacınız olabilir?

Tek bir iş parçacığı içeren bir havuza sahip bir ExecutorService oluşturmak için Executors.newSingleThreadExecutor yöntemini kullanabilirsiniz . Havuzun mantığı şu şekildedir:

  • Hizmet, aynı anda yalnızca bir görevi yürütür.
  • Yürütme için N görev gönderirsek, tüm N görev tek bir iş parçacığı tarafından birbiri ardına yürütülür.
  • İş parçacığı kesintiye uğrarsa, kalan görevleri yürütmek için yeni bir iş parçacığı oluşturulur.

Programımızın aşağıdaki işlevleri gerektirdiği bir durumu hayal edelim:

Kullanıcı isteklerini 30 saniye içinde işleme koymamız gerekiyor, ancak birim zamanda birden fazla isteği işlememeliyiz.

Bir kullanıcı isteğini işlemek için bir görev sınıfı oluşturuyoruz:


class Task implements Runnable {
   private final int taskNumber;

   public Task(int taskNumber) {
       this.taskNumber = taskNumber;
   }

   @Override
   public void run() {
       try {
           Thread.sleep(1000);
       } catch (InterruptedException ignored) {
       }
       System.out.printf("Processed request #%d on thread id=%d\\n", taskNumber, Thread.currentThread().getId());
   }
}
    

Sınıf, gelen bir isteği işleme davranışını modeller ve numarasını görüntüler.

Ardından, ana yöntemde, gelen istekleri sırayla işlemek için kullanacağımız 1 iş parçacığı için bir ExecutorService oluşturuyoruz . Görev koşulları "30 saniye içinde" şart koştuğundan, 30 saniyelik beklemeyi ekliyoruz ve ardından ExecutorService'i zorla durduruyoruz .


public static void main(String[] args) throws InterruptedException {
   ExecutorService executorService = Executors.newSingleThreadExecutor();

   for (int i = 0; i < 1_000; i++) {
       executorService.execute(new Task(i));
   }
   executorService.awaitTermination(30, TimeUnit.SECONDS);
   executorService.shutdownNow();
}
    

Programı başlattıktan sonra konsol, istek işleme ile ilgili mesajları görüntüler:

İş parçacığı kimliği=16'da işlenen istek #0 İş
parçacığı kimliği=16'da işlenen istek #1 İş parçacığı kimliği
=16'da işlenen istek #2

İş parçacığı kimliği=16'da işlenen istek #29

İstekleri 30 saniye işledikten sonra executorService , mevcut görevi (yürütülen) durduran ve bekleyen tüm görevleri iptal eden shutdownNow() yöntemini çağırır . Bundan sonra, program başarıyla sona erer.

Ancak her şey her zaman o kadar mükemmel değildir çünkü programımız, havuzumuzun tek iş parçacığı tarafından alınan görevlerden birinin yanlış çalıştığı ve hatta iş parçacığımızı sonlandırdığı bir duruma kolaylıkla sahip olabilir. Bu durumda executorService'in tek bir iş parçacığı ile nasıl çalıştığını anlamak için bu durumu simüle edebiliriz .

Bunu yapmak için, görevlerden biri yürütülürken güvenli olmayan ve kullanılmayan Thread.currentThread().stop() yöntemini kullanarak iş parçacığımızı sonlandırıyoruz. Bunu, görevlerden birinin iş parçacığını sonlandırdığı durumu simüle etmek için kasıtlı olarak yapıyoruz.

Task sınıfındaki run yöntemini değiştireceğiz :


@Override
public void run() {
   try {
       Thread.sleep(1000);
   } catch (InterruptedException ignored) {
   }

   if (taskNumber == 5) {
       Thread.currentThread().stop();
   }

   System.out.printf("Processed request #%d on thread id=%d\\n", taskNumber, Thread.currentThread().getId());
}
    

Görev # 5'i yarıda keseceğiz.

Görev #5'in sonunda iş parçacığı kesintiye uğradığında çıktının nasıl göründüğüne bakalım:

İş parçacığı kimliğinde işlenen istek #0=16 İş
parçacığı kimliğinde İşlenen istek
#1=16 İş parçacığı kimliğinde işlenen istek #2=16 İş
parçacığı kimliğinde İşlenen istek #3=16 İş parçacığı kimliğinde
İşlenen istek #4=16
İşlenen istek #6 thread id=17
thread id=17'de işlenen istek #7

thread id=17'de işlenen istek #29

Görev 5'in sonunda thread kesintiye uğradıktan sonra görevlerin daha önce tanımlayıcısı 16 olan thread'de yürütülmüş olmasına rağmen tanımlayıcısı 17 olan bir thread'de yürütülmeye başladığını görüyoruz. tek iş parçacığı, bu yalnızca tek bir anlama gelebilir: executorService durdurulan iş parçacığını yenisiyle değiştirdi ve görevleri yürütmeye devam etti.

Bu nedenle, görevleri sırayla ve her seferinde yalnızca bir tane işlemek istediğimizde ve bir önceki görevin tamamlanmasına bakılmaksızın (örn. görevlerimizin iş parçacığını öldürür).

İplik Fabrikası

Konu oluşturmaktan ve yeniden oluşturmaktan bahsederken, bahsetmeden edemeyiz.İplik Fabrikası.

Aİplik Fabrikasıistek üzerine yeni iş parçacığı oluşturan bir nesnedir.

Kendi iş parçacığı oluşturma fabrikamızı oluşturabilir ve bunun bir örneğini Executors.newSingleThreadExecutor (ThreadFactory threadFactory) yöntemine iletebiliriz.


ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "MyThread");
            }
        });
                    
Yapıcıya bir iş parçacığı adı ileterek yeni bir iş parçacığı oluşturma yöntemini geçersiz kılıyoruz.

ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r, "MyThread");
                thread.setPriority(Thread.MAX_PRIORITY);
                return thread;
            }
        });
                    
Oluşturulan iş parçacığının adını ve önceliğini değiştirdik.

Böylece 2 aşırı yüklenmiş Executors.newSingleThreadExecutor yöntemimiz olduğunu görüyoruz . Biri parametresiz, ikincisi ThreadFactory parametreli.

Bir ThreadFactory kullanarak , örneğin öncelikleri ayarlayarak, iş parçacığı alt sınıflarını kullanarak, iş parçacığına bir UncaughtExceptionHandler ekleyerek vb.