ExecutorService, tüm görevlerin bitmesini beklemek


200

Tüm görevlerin ExecutorServicebitmesini beklemenin en basit yolu nedir ? Görevim öncelikle hesaplamalı, bu yüzden sadece çok sayıda iş yapmak istiyorum - her çekirdekte bir tane. Şu anda kurulumum şöyle görünüyor:

ExecutorService es = Executors.newFixedThreadPool(2);
for (DataTable singleTable : uniquePhrases) {   
    es.execute(new ComputeDTask(singleTable));
}
try{
    es.wait();
} 
catch (InterruptedException e){
    e.printStackTrace();
}

ComputeDTaskrunnable uygular. Bu doğru görevleri yürütmek için görünür, ancak kod çöker wait()ile IllegalMonitorStateException. Bu tuhaf, çünkü bazı oyuncak örnekleriyle oynadım ve işe yaradı.

uniquePhraseson binlerce öğe içerir. Başka bir yöntem mi kullanmalıyım? Mümkün olduğunca basit bir şey arıyorum


1
beklemek istiyorsanız (cevaplar size söylemediğinizi söyler): Beklemek istediğinizde her zaman nesne üzerinde senkronize olmanız gerekir (bu durumda es) - kilit beklerken otomatik olarak serbest bırakılır
mihi

13
ThreadPool başlatmak için daha iyi bir yolExecutors.newFixedThreadPool(System.getRuntime().availableProcessors());

7
Runtime.getRuntime () availableProcessors (.);
Vik Gamov

[Bu] [1] ilginç bir alternatif .. [1]: stackoverflow.com/questions/1250643/…
Răzvan Petruescu

1
CountDownLatch kullanmak istiyorsanız, bu örnek kod: stackoverflow.com/a/44127101/4069305
Tuan Pham

Yanıtlar:


213

En basit yaklaşım, ExecutorService.invokeAll()tek bir astarda ne istediğinizi yapmaktır. Görüşünüze göre, ComputeDTaskuygulamayı Callable<>biraz daha esnek hale getirecek şekilde değiştirmeniz veya silmeniz gerekir . Muhtemelen uygulamanızda anlamlı bir uygulama var Callable.call(), ancak kullanmıyorsanız sarmanın bir yolu Executors.callable().

ExecutorService es = Executors.newFixedThreadPool(2);
List<Callable<Object>> todo = new ArrayList<Callable<Object>>(singleTable.size());

for (DataTable singleTable: uniquePhrases) { 
    todo.add(Executors.callable(new ComputeDTask(singleTable))); 
}

List<Future<Object>> answers = es.invokeAll(todo);

Diğerlerinin de belirttiği gibi, invokeAll()uygunsa , zaman aşımı sürümünü kullanabilirsiniz . Bu örnekte, boş değer döndürecek answersbir grup Futures içerecektir (tanımına bakın Executors.callable(). Muhtemelen yapmak istediğiniz şey hafif bir yeniden düzenleme, böylece yararlı bir cevap veya altta yatan bir referans ComputeDTaskalabilirsiniz, ancak ben Örneğinizden söylemeyin.

Açık değilse, invokeAll()tüm görevler tamamlanana kadar geri dönmeyeceğini unutmayın . (yani tüm Futuresenin s answerskoleksiyonunda bildirir .isDone()Bu, tüm manuel kapatma, awaitTermination, vb önler istenirse.) ve ... bunu yeniden kullanıma izin verenExecutorService istenirse çoklu döngüleri için düzgünce.

SO ile ilgili birkaç soru var:

Bunların hiçbiri sorunuz için kesinlikle doğru değil, ancak insanların nasıl kullanılması Executor/ ExecutorServicekullanılması gerektiği konusunda biraz renk veriyorlar .


9
Tüm işlerinizi toplu olarak ekliyorsanız ve Callables listesine asarsanız mükemmeldir, ancak geri arama veya olay döngüsü durumunda ExecutorService.submit () öğesini çağırırsanız çalışmaz.
destY

2
Ben executionService artık gerekli olmadığında, shut () hala çağrılması gerektiğini belirtmek gerekir, aksi takdirde (asla corePoolSize = 0 veya allowCoreThreadTimeOut = true durumlar hariç) iş parçacığı.
John29

inanılmaz! Tam da aradığım şey. Cevabı paylaştığınız için çok teşekkürler. Bunu deneyeyim.
MohamedSanaulla

59

Tüm görevlerin tamamlanmasını beklemek istiyorsanız, shutdownyerine yöntemi kullanın wait. Sonra ile takip edin awaitTermination.

Ayrıca, Runtime.availableProcessorsthreadpool'unuzu doğru şekilde başlatabilmeniz için donanım dişlerinin sayısını almak için kullanabilirsiniz .


27
shutdown (), ExecutorService'in yeni görevleri kabul etmesini durdurur ve boştaki iş parçacıklarını kapatır. Kapatma işleminin tamamlanmasını beklemesi belirtilmez ve ThreadPoolExecutor uygulamasında beklemez.
Alain O'Dea

1
@Alain - teşekkürler. Beklemeden bahsetmeliydimTerminasyon. Sabit.
NG.

5
Bir görevin tamamlanması için başka görevler zamanlaması gerekiyorsa ne olur? Örneğin, dalları iş parçacıklarına dağıtan çok iş parçacıklı bir ağaç geçişi yapabilirsiniz. Bu durumda, ExecutorService anında kapatıldığı için yinelenen zamanlanmış işleri kabul edemez.
Brian Gordon

2
awaitTerminationparametre olarak zaman aşımı süresi gerektirir. Sonlu bir zaman sağlamak ve tüm ipliklerin bitmesini beklemek için etrafına bir döngü yerleştirmek mümkün olsa da, daha zarif bir çözüm olup olmadığını merak ediyordum.
Abhishek S

1
Haklısınız, ancak bu cevaba bakın - stackoverflow.com/a/1250655/263895 - her zaman inanılmaz derecede uzun bir zaman aşımı verebilirsiniz
NG.

48

Hedefindeki tüm görevleri beklemek ExecutorServicetam olarak hedefiniz değilse, daha ziyade belirli bir görev grubu tamamlanana kadar beklemek istiyorsanız, CompletionService- özellikle, bir ExecutorCompletionService.

Fikir, bir ExecutorCompletionServicesarma oluşturmak Executor, bilinen bazı görevler yoluyla göndermek , ardından aynı sayıda sonucu tamamlama kuyruğundan (engelleme) veya ( engellemeyen) kullanarak çizmektir . Gönderdiğiniz görevlere karşılık gelen tüm beklenen sonuçları çizdiğinizde, bunların tamamlandığını bilirsiniz.CompletionServicetake()poll()

Bunu bir kez daha belirteyim, çünkü arayüzden belli değil: CompletionServiceKaç tane şey çizmeye çalışacağınızı bilmek için kaç şey koyduğunuzu bilmelisiniz. Bu özellikle take()yöntemle önemlidir : bir kez çok fazla çağırın ve başka bir iş parçacığı aynı işi başka bir iş gönderene kadar arama iş parçacığınızı engeller CompletionService.

Uygulamada Java Eşzamanlılığı kitabında nasıl kullanılacağını gösteren bazı örneklerCompletionService vardır .


Bu benim cevabım için iyi bir kontrpuan - Sorunun doğrudan cevabının invokeAll () olduğunu söyleyebilirim; ancak @seh, ES'ye iş grupları gönderirken ve tamamlanmalarını beklerken bunu doğru yapıyor ... --JA
andersoj

@ om-nom-nom, bağlantıları güncellediğiniz için teşekkür ederiz. Cevabın hala yararlı olduğunu gördüğüme sevindim.
seh

1
İyi cevap, farkında değildimCompletionService
Vic

1
Varolan bir ExecutorService'i kapatmak istemezseniz, ancak bir grup görevi göndermek ve bunların ne zaman tamamlandığını bilmek istiyorsanız, bu yaklaşımdır.
ToolmakerSteve

11

Eğer yürütme bitirmek için infaz hizmeti için beklemek istersen, çağrı shutdown()ve daha sonra, awaitTermination (birimler, UNITTYPE) ör awaitTermination(1, MINUTE). ExecutorService kendi monitöründe engellemez, bu nedenle kullanamazsınız wait.


Bence bu bekliyorTerminasyon.
NG.

@SB - Teşekkürler - Hafızamın yanıltıcı olduğunu görüyorum! Adı güncelledim ve emin olmak için bir bağlantı ekledim.
Mdma

"Sonsuza kadar" beklemek için awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); stackoverflow.com/a/1250655/32453
rogerdpack

Bence bu en kolay yaklaşım
Shervin Asgari

1
@MosheElisha, emin misin? docs.oracle.com/javase/8/docs/api/java/util/concurrent/… diyor ki , daha önce gönderilen görevlerin yürütüldüğü düzenli bir kapatma başlatır, ancak yeni görev kabul
Jaime Hablutzel

7

İşlerin belirli bir aralıkta bitmesini bekleyebilirsiniz:

int maxSecondsPerComputeDTask = 20;
try {
    while (!es.awaitTermination(uniquePhrases.size() * maxSecondsPerComputeDTask, TimeUnit.SECONDS)) {
        // consider giving up with a 'break' statement under certain conditions
    }
} catch (InterruptedException e) {
    throw new RuntimeException(e);    
}

Veya ExecutorService kullanabilirsiniz . gönderin ( Runnable ) ve döndürdüğü Future nesnelerini toplayın ve bitirmelerini beklemek için her birine get () çağırın .

ExecutorService es = Executors.newFixedThreadPool(2);
Collection<Future<?>> futures = new LinkedList<<Future<?>>();
for (DataTable singleTable : uniquePhrases) {
    futures.add(es.submit(new ComputeDTask(singleTable)));
}
for (Future<?> future : futures) {
   try {
       future.get();
   } catch (InterruptedException e) {
       throw new RuntimeException(e);
   } catch (ExecutionException e) {
       throw new RuntimeException(e);
   }
}

InterruptedException düzgün işlemek için son derece önemlidir. Sizin veya kütüphanenizin kullanıcılarının uzun bir süreci güvenli bir şekilde sonlandırmasına izin veren şey budur.


6

Sadece kullan

latch = new CountDownLatch(noThreads)

Her iş parçacığında

latch.countDown();

ve bariyer olarak

latch.await();

6

IllegalMonitorStateException için temel neden :

Bir iş parçacığının, bir nesnenin monitöründe beklemeye çalıştığını veya belirtilen monitöre sahip olmadan bir nesnenin ekranında bekleyen diğer iş parçacıklarını bilgilendirmeye çalıştığını göstermek için atılır.

Kodunuzdan, kilide sahip olmadan ExecutorService üzerinde wait () işlevini çağırdınız.

Aşağıdaki kod düzeltilecek IllegalMonitorStateException

try 
{
    synchronized(es){
        es.wait(); // Add some condition before you call wait()
    }
} 

Sunulan tüm görevlerin tamamlanmasını beklemek için aşağıdaki yaklaşımlardan birini izleyin ExecutorService.

  1. Bütün aracılığıyla Bıkmadan Futuregelen görevler submitüzerinde ExecutorServiceçağrı engelleme ile durum ve kontrol get()üzerinde Futurenesne

  2. İnvokeAll özelliğini kullanmaExecutorService

  3. CountDownLatch kullanma

  4. ForkJoinPool veya newWorkStealingPool of kullanma Executors(java 8'den beri)

  5. Oracle Belgeleri sayfasında önerilen şekilde havuzu kapatın

    void shutdownAndAwaitTermination(ExecutorService pool) {
       pool.shutdown(); // Disable new tasks from being submitted
       try {
       // Wait a while for existing tasks to terminate
       if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
           pool.shutdownNow(); // Cancel currently executing tasks
           // Wait a while for tasks to respond to being cancelled
           if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
       }
    } catch (InterruptedException ie) {
         // (Re-)Cancel if current thread also interrupted
         pool.shutdownNow();
         // Preserve interrupt status
         Thread.currentThread().interrupt();
    }

    Seçenek 1 ila 4 yerine seçenek 5'i kullanırken tüm görevlerin tamamlanmasını beklemek istiyorsanız, değiştirin

    if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {

    için

    while(condition)her 1 dakikada bir kontrol eder.


6

ExecutorService.invokeAllYöntemi kullanabilirsiniz , Tüm görevleri yürütür ve tüm iş parçacıklarının görevlerini bitirmesini bekler.

İşte tam javadoc

Zaman aşımını belirlemek için bu yöntemin aşırı yüklenmiş sürümünü de kullanabilirsiniz.

İşte ile örnek kod ExecutorService.invokeAll

public class Test {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService service = Executors.newFixedThreadPool(3);
        List<Callable<String>> taskList = new ArrayList<>();
        taskList.add(new Task1());
        taskList.add(new Task2());
        List<Future<String>> results = service.invokeAll(taskList);
        for (Future<String> f : results) {
            System.out.println(f.get());
        }
    }

}

class Task1 implements Callable<String> {
    @Override
    public String call() throws Exception {
        try {
            Thread.sleep(2000);
            return "Task 1 done";
        } catch (Exception e) {
            e.printStackTrace();
            return " error in task1";
        }
    }
}

class Task2 implements Callable<String> {
    @Override
    public String call() throws Exception {
        try {
            Thread.sleep(3000);
            return "Task 2 done";
        } catch (Exception e) {
            e.printStackTrace();
            return " error in task2";
        }
    }
}

3

Ayrıca taranacak bir dizi belgem var. İşlenmesi gereken bir başlangıç ​​"tohum" belgesi ile başlıyorum, bu belge de işlenmesi gereken diğer belgelere bağlantılar içeriyor.

Ana programımda, sadece Crawlerbir grup iş parçacığını kontrol eden aşağıdaki gibi bir şey yazmak istiyorum .

Crawler c = new Crawler();
c.schedule(seedDocument); 
c.waitUntilCompletion()

Bir ağaçta gezinmek istesem de aynı durum olurdu; Kök düğümünde açılırdım, her düğümün işlemcisi gerektiğinde kuyruğa çocuk eklerdi ve daha fazla olana kadar bir grup iş parçacığı ağaçtaki tüm düğümleri işleyecekti.

JVM'de biraz şaşırtıcı olduğunu düşündüğüm bir şey bulamadım. Bu yüzden ThreadPoolalan adı için uygun yöntemler eklemek için doğrudan veya alt sınıf kullanabileceğiniz bir sınıf yazdım schedule(Document). Umarım yardımcı olur!

ThreadPool Javadoc | Uzman


Doc Bağlantı öldü
Manti_Core

@Manti_Core - teşekkürler, güncellendi.
Adrian Smith

2

Koleksiyondaki tüm evreleri ekleyin ve kullanarak gönderin invokeAll. invokeAllYöntemini kullanabiliyorsanız ExecutorService, JVM tüm evreler tamamlanıncaya kadar bir sonraki satıra geçmez.

Burada iyi bir örnek var: ExecokeService üzerinden invokeAll


1

Halinde görevleri gönderin Runner ve daha sonra yöntem çağırarak bekleyin waitTillDone () böyle:

Runner runner = Runner.runner(2);

for (DataTable singleTable : uniquePhrases) {

    runner.run(new ComputeDTask(singleTable));
}

// blocks until all tasks are finished (or failed)
runner.waitTillDone();

runner.shutdown();

Kullanmak için bu gradle / maven bağımlılığını ekleyin: 'com.github.matejtymes:javafixes:1.0'

Daha fazla ayrıntı için buraya bakın: https://github.com/MatejTymes/JavaFixes veya buraya: http://matejtymes.blogspot.com/2016/04/executor-that-notifications-you-when-task.html



0

Sadece yürütmenin görevlerin tamamlanması için uygun olduğunu düşündüğünüz belirli bir zaman aşımı ile sonlanmasını bekleyeceğim.

 try {  
         //do stuff here 
         exe.execute(thread);
    } finally {
        exe.shutdown();
    }
    boolean result = exe.awaitTermination(4, TimeUnit.HOURS);
    if (!result)

    {
        LOGGER.error("It took more than 4 hour for the executor to stop, this shouldn't be the normal behaviour.");
    }

0

İhtiyaç duyduğunuz gibi geliyor ForkJoinPoolve görevleri yürütmek için global havuzu kullanıyorsunuz.

public static void main(String[] args) {
    // the default `commonPool` should be sufficient for many cases.
    ForkJoinPool pool = ForkJoinPool.commonPool(); 
    // The root of your task that may spawn other tasks. 
    // Make sure it submits the additional tasks to the same executor that it is in.
    Runnable rootTask = new YourTask(pool); 
    pool.execute(rootTask);
    pool.awaitQuiescence(...);
    // that's it.
}

Güzellik, pool.awaitQuiescenceyöntemin görevlerini yerine getirmek için arayanın iş parçacığını kullanmasını engelleyecek ve daha sonra gerçekten boş olduğunda geri dönecektir .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.