Executors sınıfının newFixedThreadPool yöntemi , sabit sayıda iş parçacığına sahip bir executorService oluşturur . newSingleThreadExecutor yönteminden farklı olarak , havuzda kaç tane iş parçacığı istediğimizi belirtiyoruz. Kaputun altında, aşağıdaki kod denir:


new ThreadPoolExecutor(nThreads, nThreads,
                                      	0L, TimeUnit.MILLISECONDS,
                                      	new LinkedBlockingQueue());

corePoolSize ( yürütme hizmeti başladığında hazır olacak (başlatılacak) iş parçacığı sayısı ) ve maximumPoolSize ( yürütme hizmetinin oluşturabileceği maksimum iş parçacığı sayısı ) parametreleri aynı değeri alır newFixedThreadPool(nThreads) öğesine iletilen iş parçacığı sayısı ) . Ve kendi ThreadFactory uygulamamızı tamamen aynı şekilde geçirebiliriz .

Peki, neden böyle bir ExecutorService'e ihtiyacımız olduğunu görelim .

İşte sabit sayıda (n) iş parçacığı olan bir ExecutorService mantığı :

  • İşleme görevleri için maksimum n iş parçacığı etkin olacaktır.
  • n'den fazla görev gönderilirse, ileti dizileri serbest kalana kadar kuyrukta tutulurlar.
  • Eğer thread'lerden biri başarısız olur ve sonlanırsa, onun yerini alacak yeni bir thread oluşturulur.
  • Havuz kapatılana kadar havuzdaki herhangi bir iş parçacığı etkindir.

Örnek olarak, havaalanında güvenlikten geçmek için beklediğinizi hayal edin. Güvenlik kontrolünden hemen öncesine kadar herkes aynı sırada durur, yolcular çalışan tüm kontrol noktalarına dağıtılır. Kontrol noktalarından birinde gecikme olması durumunda, ilk kontrol noktası boş olana kadar sıra sadece ikincisi tarafından işlenecektir. Ve eğer bir kontrol noktası tamamen kapanırsa, onun yerine başka bir kontrol noktası açılacak ve yolcuların işlemleri iki kontrol noktasından devam edecek.

Hemen belirtelim ki, koşullar ideal olsa bile - söz verilen n iş parçacığı istikrarlı bir şekilde çalışıyor ve bir hatayla biten iş parçacıkları her zaman değiştiriliyor (sınırlı kaynakların gerçek bir havaalanında elde etmeyi imkansız kıldığı bir şey) - sistemin hala birkaç tane var hoş olmayan özellikler, çünkü sıra iş parçacıklarının görevleri işleyebileceğinden daha hızlı büyüse bile hiçbir koşulda daha fazla iş parçacığı olmayacak.

ExecutorService'in sabit sayıda iş parçacığıyla nasıl çalıştığına dair pratik bir anlayış edinmenizi öneririm . Runnable'ı uygulayan bir sınıf oluşturalım . Bu sınıfın nesneleri, ExecutorService için görevlerimizi temsil eder .


public class Task implements Runnable {
    int taskNumber;
 
    public Task(int taskNumber) {
        this.taskNumber = taskNumber;
    }
 
    @Override
    public void run() {
try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
    }
}
    

run() yönteminde , iş yükünü simüle ederek iş parçacığını 2 saniye bloke ederiz ve ardından mevcut görevin numarasını ve görevi yürüten iş parçacığının adını görüntüleriz.


ExecutorService executorService = Executors.newFixedThreadPool(3);
 
        for (int i = 0; i < 30; i++) {
            executorService.execute(new Task(i));
        }
        
        executorService.shutdown();
    

Başlamak için, ana yöntemde bir ExecutorService oluşturuyoruz ve yürütme için 30 görev gönderiyoruz.

havuz-1-thread-2 dizisinde işlenen kullanıcı isteği #1
havuz-1-thread-1 iş parçacığında işlenen kullanıcı isteği #0
Havuz-1-thread-3 iş parçacığında işlenen kullanıcı isteği #2
havuz- üzerinde işlenen kullanıcı isteği #5 1-thread-3 iş parçacığı
Havuz-1-thread-2 iş parçacığında işlenen kullanıcı isteği #3
havuz-1-thread-1 iş parçacığında İşlenen kullanıcı isteği #4
havuz-1-thread-1 iş parçacığında işlenen kullanıcı isteği #8
İşlenen kullanıcı havuz-1-thread-3 iş parçacığında istek #6 Havuz-
1-thread-2 iş parçacığında işlenen kullanıcı isteği #7 Havuz-1
-thread-3 iş parçacığında işlenen kullanıcı isteği #10
Havuz-1- üzerinde işlenen kullanıcı isteği #9 thread-1 thread
Havuz-1-thread-2 thread üzerinde işlenen kullanıcı isteği
#11 pool-1-thread-3 thread üzerinde işlenen kullanıcı isteği #12
Havuz-1-thread-2 iş parçacığında işlenen kullanıcı isteği #14
Havuz-1-thread-1 iş parçacığında işlenen kullanıcı isteği #13 Havuz-
1-thread-3 iş parçacığında işlenen kullanıcı isteği #15
Havuz- üzerinde işlenen kullanıcı isteği #16 1-thread-2 iş parçacığı
Havuz-1-thread-1 iş parçacığında İşlenen kullanıcı isteği #17
Havuz-1-thread-3 iş parçacığında
İşlenen kullanıcı isteği #18 Havuz-1-thread-2 iş parçacığında İşlenen kullanıcı isteği #19
İşlenen kullanıcı havuz-1-thread-1 iş parçacığında istek #20
Havuz-1-thread-3 iş parçacığında işlenen kullanıcı isteği #21 Havuz-
1-thread-2 iş parçacığında işlenen kullanıcı isteği #22
havuz-1- üzerinde işlenen kullanıcı isteği #23 thread-1 thread
Havuz-1-thread-2 thread
üzerinde işlenen kullanıcı isteği #25 pool-1-thread-3 thread üzerinde işlenen kullanıcı isteği #24
Havuz-1-thread-1 iş parçacığında işlenen kullanıcı isteği #26
Havuz-1-thread-2 iş parçacığında işlenen kullanıcı isteği #27
Havuz-1-thread-3 iş parçacığında işlenen kullanıcı isteği #28
Havuz- üzerinde işlenen kullanıcı isteği #29 1-iplik-1 iplik

Konsol çıktısı, görevlerin önceki görev tarafından serbest bırakıldıktan sonra farklı iş parçacıklarında nasıl yürütüldüğünü bize gösterir.

Şimdi görev sayısını 100'e çıkaracağız ve 100 görev gönderdikten sonra waitTermination(11, SECONDS) yöntemini çağıracağız . Bir sayı ve zaman birimini argüman olarak iletiyoruz. Bu yöntem, ana iş parçacığını 11 saniye süreyle engelleyecektir. Ardından, tüm görevlerin tamamlanmasını beklemeden ExecutorService'i kapanmaya zorlamak için shutdownNow()' u çağıracağız .


ExecutorService executorService = Executors.newFixedThreadPool(3);
 
        for (int i = 0; i < 100; i++) {
            executorService.execute(new Task(i));
        }
 
        executorService.awaitTermination(11, SECONDS);
 
        executorService.shutdownNow();
        System.out.println(executorService);
    

Sonunda, executorService'in durumu hakkında bilgi göstereceğiz .

İşte elde ettiğimiz konsol çıktısı:

Havuz-1-thread-1 iş parçacığında işlenen kullanıcı isteği #0
havuz-1-thread-3 iş parçacığında
işlenen kullanıcı isteği #2 Havuz-1-thread-2 iş parçacığında işlenen
kullanıcı isteği #1 Havuz- üzerinde işlenen kullanıcı isteği #4 1-thread-3 iş parçacığı
Pool-1-thread-2 iş parçacığında işlenen kullanıcı isteği #5
havuz-1-thread-1 iş parçacığında işlenen kullanıcı isteği #3
havuz-1-thread-3 iş parçacığında işlenen kullanıcı isteği #6
İşlenen kullanıcı havuz-1-thread-2 iş parçacığında istek #7 Havuz-
1-thread-1 iş parçacığında işlenen kullanıcı isteği #8
Havuz-1-thread-3 iş parçacığında işlenen kullanıcı isteği #9
Havuz-1- üzerinde işlenen kullanıcı isteği #11 thread-1 thread
Havuz-1-thread-2 thread üzerinde işlenen kullanıcı isteği
#10 pool-1-thread-1 thread üzerinde işlenen kullanıcı isteği #13
Havuz-1-thread-2 iş parçacığında işlenen kullanıcı isteği #14
havuz-1-thread-3 iş parçacığında işlenen kullanıcı isteği #12
java.util.concurrent.ThreadPoolExecutor@452b3a41[Kapanıyor, havuz boyutu = 3, aktif iş parçacıkları = 3 , sıraya alınmış görevler = 0, tamamlanan görevler = 15]
Pool-1-thread-3 iş parçacığında işlenen kullanıcı isteği #17 Havuz-1
-thread-1 iş parçacığında işlenen kullanıcı isteği #15 Havuz-
1-thread'de işlenen kullanıcı isteği #16 -2 iş parçacığı

Bunu, 3 aktif görevden uyku yöntemleri tarafından atılan 3 InterruptedExceptions takip eder .

Program sona erdiğinde 15 görevin yapıldığını ancak havuzda hala görevlerini yürütmeyi tamamlamayan 3 aktif iş parçacığının olduğunu görebiliriz. Bu üç iş parçacığında interrupt () yöntemi çağrılır, bu da görevin tamamlanacağı anlamına gelir, ancak bizim durumumuzda, uyku yöntemi bir InterruptedException fırlatır . Ayrıca shutdownNow() yöntemi çağrıldıktan sonra görev kuyruğunun temizlendiğini de görüyoruz .

Bu nedenle , havuzda sabit sayıda iş parçacığı olan bir ExecutorService kullanırken , nasıl çalıştığını hatırladığınızdan emin olun. Bu tip, bilinen bir sabit yüke sahip görevler için uygundur.

İşte başka bir ilginç soru: Tek bir iş parçacığı için bir yürütücü kullanmanız gerekiyorsa, hangi yöntemi çağırmalısınız? newSingleThreadExecutor() veya newFixedThreadPool(1) ?

Her iki uygulayıcı da eşdeğer davranışa sahip olacaktır. Tek fark, newSingleThreadExecutor() yönteminin, daha sonra ek iş parçacıkları kullanmak üzere yeniden yapılandırılamayacak bir yürütücü döndürmesidir.