ExecutorService, bagaimana menunggu semua tugas selesai

197

Apa cara paling sederhana untuk menunggu semua tugas ExecutorServiceselesai? Tugas saya terutama komputasi, jadi saya hanya ingin menjalankan sejumlah besar pekerjaan - satu di setiap inti. Sekarang pengaturan saya terlihat seperti ini:

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

ComputeDTaskmengimplementasikan runnable. Ini tampaknya menjalankan tugas dengan benar, tetapi kode macet wait()dengan IllegalMonitorStateException. Ini aneh, karena saya bermain-main dengan beberapa contoh mainan dan tampaknya berhasil.

uniquePhrasesmengandung beberapa puluh ribu elemen. Haruskah saya menggunakan metode lain? Saya mencari sesuatu yang sesederhana mungkin

george tersenyum
sumber
1
jika Anda ingin menggunakan tunggu (jawaban memberi tahu Anda tidak): Anda selalu perlu disinkronkan pada objek (dalam hal ini es) ketika Anda ingin menunggu - kunci akan dilepaskan secara otomatis sambil menunggu
mihi
13
cara yang lebih baik untuk menginisialisasi ThreadPool adalahExecutors.newFixedThreadPool(System.getRuntime().availableProcessors());
7
Runtime.getRuntime (). AvailableProcessors ();
Vik Gamov
[Ini] [1] adalah alternatif yang menarik .. [1]: stackoverflow.com/questions/1250643/…
Răzvan Petruescu
1
jika Anda ingin menggunakan CountDownLatch, ini adalah contoh kode: stackoverflow.com/a/44127101/4069305
Tuan Pham

Jawaban:

213

Pendekatan paling sederhana adalah menggunakan ExecutorService.invokeAll()yang melakukan apa yang Anda inginkan dalam satu-liner. Dalam bahasa Anda, Anda harus memodifikasi atau membungkus ComputeDTaskuntuk mengimplementasikan Callable<>, yang dapat memberi Anda sedikit lebih banyak fleksibilitas. Mungkin di aplikasi Anda ada implementasi yang bermakna Callable.call(), tapi inilah cara untuk membungkusnya jika tidak menggunakan 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);

Seperti yang telah ditunjukkan orang lain, Anda dapat menggunakan versi batas waktu invokeAll()jika sesuai. Dalam contoh ini, answersakan berisi banyak Futures yang akan mengembalikan nol (lihat definisi Executors.callable(). Mungkin yang ingin Anda lakukan adalah sedikit refactoring sehingga Anda bisa mendapatkan jawaban yang berguna kembali, atau referensi ke yang mendasarinya ComputeDTask, tapi saya bisa dari contoh Anda.

Jika tidak jelas, perhatikan bahwa invokeAll()tidak akan kembali sampai semua tugas selesai. (yaitu, semua yang ada Futuredi answerskoleksi Anda akan melaporkan .isDone()jika diminta.) Ini menghindari semua shutdown manual, menungguTerminasi, dll ... dan memungkinkan Anda untuk menggunakan kembali ini ExecutorServicedengan rapi untuk beberapa siklus, jika diinginkan.

Ada beberapa pertanyaan terkait pada SO:

Tidak ada yang benar-benar tepat untuk pertanyaan Anda, tetapi mereka memang memberikan sedikit warna tentang bagaimana orang berpikir Executor/ ExecutorServiceseharusnya digunakan.

andersoj
sumber
9
Ini sempurna jika Anda menambahkan semua pekerjaan Anda dalam satu batch dan Anda bertahan di daftar Callable, tetapi itu tidak akan berfungsi jika Anda memanggil ExecutorService.submit () dalam situasi callback atau event-loop.
Desty
2
Saya pikir layak menyebutkan bahwa shutdown () masih harus dipanggil ketika ExecutorService tidak lagi diperlukan, jika tidak, utas tidak akan pernah berakhir (kecuali untuk kasus ketika corePoolSize = 0 atau allowCoreThreadTimeOut = true).
John29
luar biasa! Apa yang saya cari. Terima kasih banyak untuk membagikan jawabannya. Biarkan saya coba ini.
MohamedSanaulla
59

Jika Anda ingin menunggu semua tugas selesai, gunakan shutdownmetode sebagai ganti wait. Kemudian ikuti dengan awaitTermination.

Selain itu, Anda dapat menggunakan Runtime.availableProcessorsuntuk mendapatkan jumlah utas perangkat keras sehingga Anda dapat menginisialisasi threadpool dengan benar.

NG.
sumber
27
shutdown () menghentikan ExecutorService dari menerima tugas baru dan menutup utas pekerja yang menganggur. Tidak ditentukan untuk menunggu shutdown selesai dan implementasi di ThreadPoolExecutor tidak menunggu.
Alain O'Dea
1
@Alain - terima kasih. Seharusnya saya sebutkan tunggu tunggu. Tetap.
NG.
5
Bagaimana jika agar suatu tugas dapat diselesaikan, ia harus menjadwalkan tugas selanjutnya? Misalnya, Anda bisa membuat traversal pohon multithreaded yang menyerahkan cabang ke thread pekerja. Dalam hal ini, karena ExecutorService dimatikan secara instan, maka ia gagal menerima pekerjaan yang dijadwalkan secara rekursif.
Brian Gordon
2
awaitTerminationmembutuhkan batas waktu sebagai parameter. Meskipun mungkin untuk memberikan waktu yang terbatas dan menempatkan lingkaran di sekitarnya untuk menunggu sampai semua utas selesai, saya bertanya-tanya apakah ada solusi yang lebih elegan.
Abhishek S
1
Anda benar, tetapi lihat jawaban ini - stackoverflow.com/a/1250655/263895 - Anda selalu dapat memberikan batas waktu yang sangat lama
NG.
48

Jika menunggu semua tugas dalam ExecutorServicepenyelesaian tidak tepat sasaran Anda, melainkan menunggu hingga kumpulan tugas tertentu selesai, Anda dapat menggunakan CompletionService- khusus, sebuah ExecutorCompletionService.

Idenya adalah untuk menciptakan sebuah ExecutorCompletionServicemembungkus Anda Executor, mengirimkan beberapa nomor yang dikenal tugas melalui CompletionService, kemudian menarik bahwa jumlah yang sama dari hasil dari antrian selesai baik menggunakan take()(yang blok) atau poll()(yang tidak). Setelah Anda menggambar semua hasil yang diharapkan sesuai dengan tugas yang Anda kirimkan, Anda tahu semuanya sudah selesai.

Biarkan saya menyatakan ini sekali lagi, karena tidak jelas dari antarmuka: Anda harus tahu berapa banyak hal yang Anda masukkan CompletionServiceuntuk mengetahui berapa banyak hal yang harus dicoba. Ini sangat penting terutama dengan take()metode: panggil sekali saja dan itu akan memblokir utas panggilan Anda sampai beberapa utas lainnya mengirimkan pekerjaan lain ke yang sama CompletionService.

Ada beberapa contoh yang menunjukkan bagaimana menggunakanCompletionService dalam buku Jawa Concurrency dalam Praktek .

seh
sumber
Ini adalah tandingan yang baik untuk jawaban saya - saya akan mengatakan jawaban langsung untuk pertanyaan ini invokeAll (); tapi @seh benar ketika mengirimkan kelompok pekerjaan ke ES dan menunggu mereka untuk menyelesaikan ... --JA
andersoj
@ om-nom-nom, terima kasih telah memperbarui tautannya. Saya senang melihat bahwa jawabannya masih bermanfaat.
seh
1
Jawaban yang bagus, saya tidak tahuCompletionService
Vic
1
Ini adalah pendekatan yang digunakan, jika Anda tidak ingin mematikan ExecutorService yang sudah ada, tetapi hanya ingin mengirimkan sejumlah tugas, dan tahu kapan semuanya selesai.
ToolmakerSteve
11

Jika Anda ingin menunggu layanan pelaksana untuk menyelesaikan eksekusi, panggil shutdown()dan kemudian, tungguTerminasi (unit, tipe unit) , mis awaitTermination(1, MINUTE). ExecutorService tidak memblokir monitor itu sendiri, jadi Anda tidak dapat menggunakan waitdll.

mdma
sumber
Saya pikir itu menunggu Termininasi.
NG.
@ SB - Terima kasih - Saya melihat memori saya salah! Saya telah memperbarui nama dan menambahkan tautan untuk memastikan.
mdma
Untuk menunggu "selamanya," gunakan seperti awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); stackoverflow.com/a/1250655/32453
rogerdpack
Saya pikir ini adalah pendekatan termudah
Shervin Asgari
1
@ MosheElisha, apakah Anda yakin? docs.oracle.com/javase/8/docs/api/java/util/concurrent/… mengatakan Inisiasi pematian tertib di mana tugas yang sebelumnya dikirimkan dieksekusi, tetapi tidak ada tugas baru yang akan diterima.
Jaime Hablutzel
7

Anda bisa menunggu pekerjaan selesai pada interval tertentu:

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

Atau Anda bisa menggunakan ExecutorService . kirim ( Runnable ) dan kumpulkan objek Future yang dikembalikan dan panggil get () pada masing-masing pada gilirannya untuk menunggu sampai selesai.

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 sangat penting untuk ditangani dengan benar. Inilah yang memungkinkan Anda atau pengguna perpustakaan Anda menghentikan proses panjang dengan aman.

Alain O'Dea
sumber
6

Gunakan saja

latch = new CountDownLatch(noThreads)

Di setiap utas

latch.countDown();

dan sebagai penghalang

latch.await();
J. Ruhe
sumber
6

Penyebab root untuk IllegalMonitorStateException :

Dilemparkan untuk menunjukkan bahwa utas telah mencoba untuk menunggu pada monitor objek atau untuk memberi tahu utas lainnya menunggu pada monitor objek tanpa memiliki monitor yang ditentukan.

Dari kode Anda, Anda baru saja menelepon wait () pada ExecutorService tanpa memiliki kunci.

Kode di bawah ini akan diperbaiki IllegalMonitorStateException

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

Ikuti salah satu pendekatan di bawah ini untuk menunggu penyelesaian semua tugas, yang telah diserahkan ExecutorService.

  1. Iterate melalui semua Futuretugas dari submitpada ExecutorServicedan memeriksa status dengan memblokir panggilan get()pada Futureobjek

  2. Gunakan invokeAll onExecutorService

  3. Menggunakan CountDownLatch

  4. Menggunakan ForkJoinPool atau newWorkStealingPool dari Executors(sejak java 8)

  5. Matikan pool seperti yang direkomendasikan di halaman dokumentasi oracle

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

    Jika Anda ingin dengan anggun menunggu semua tugas selesai saat Anda menggunakan opsi 5 bukannya opsi 1 hingga 4, ubah

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

    untuk

    a while(condition)yang memeriksa setiap 1 menit.

Ravindra babu
sumber
6

Anda dapat menggunakan ExecutorService.invokeAllmetode, Ini akan menjalankan semua tugas dan menunggu sampai semua utas menyelesaikan tugas mereka.

Ini javadoc lengkap

Anda juga dapat menggunakan versi berlebih dari metode ini untuk menentukan batas waktu.

Berikut adalah contoh kode dengan 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";
        }
    }
}
Nitin Vavdiya
sumber
3

Saya juga memiliki situasi dimana saya memiliki set dokumen untuk dijelajahi. Saya mulai dengan dokumen "seed" awal yang harus diproses, dokumen itu berisi tautan ke dokumen lain yang juga harus diproses, dan seterusnya.

Di program utama saya, saya hanya ingin menulis sesuatu seperti yang berikut, di mana Crawlermengontrol banyak utas.

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

Situasi yang sama akan terjadi jika saya ingin menavigasi pohon; saya akan muncul di root node, prosesor untuk setiap node akan menambahkan anak-anak ke antrian seperlunya, dan banyak thread akan memproses semua node di pohon, sampai tidak ada lagi.

Saya tidak dapat menemukan apa pun di JVM yang saya pikir agak mengejutkan. Jadi saya menulis kelas ThreadPoolyang bisa digunakan secara langsung atau subclass untuk menambahkan metode yang cocok untuk domain, misalnya schedule(Document). Semoga ini bisa membantu!

ThreadPool Javadoc | Maven

Adrian Smith
sumber
Doc Link sudah mati
Manti_Core
@Manti_Core - terima kasih, diperbarui.
Adrian Smith
2

Tambahkan semua utas dalam koleksi dan kirimkan dengan menggunakan invokeAll. Jika Anda dapat menggunakan invokeAllmetode ExecutorService, JVM tidak akan melanjutkan ke baris berikutnya sampai semua utas selesai.

Di sini ada contoh yang bagus: invokeAll via ExecutorService

zgormez
sumber
1

Kirim tugas Anda ke dalam Runner dan kemudian tunggu memanggil metode waitTillDone () seperti ini:

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();

Untuk menggunakannya tambahkan dependensi gradle / maven ini: 'com.github.matejtymes:javafixes:1.0'

Untuk lebih jelasnya lihat di sini: https://github.com/MatejTymes/JavaFixes atau di sini: http://matejtymes.blogspot.com/2016/04/executor-that-notifies-you-when-task.html

Matej Tymes
sumber
0

Alternatif sederhana untuk ini adalah menggunakan utas bersama dengan bergabung. Refer: Menggabung Thread

Vicky Kapadia
sumber
3
ExecutorServices membuat segalanya lebih mudah
David Mann
0

Saya hanya akan menunggu sampai eksekutor berakhir dengan batas waktu yang ditentukan yang menurut Anda cocok untuk menyelesaikan tugas-tugas tersebut.

 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.");
    }
punkers
sumber
0

Kedengarannya seperti yang Anda butuhkan ForkJoinPooldan gunakan kumpulan global untuk menjalankan tugas.

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

Keindahan adalah di pool.awaitQuiescencemana metode akan memblokir memanfaatkan utas penelepon untuk melaksanakan tugasnya dan kemudian kembali ketika itu benar - benar kosong.

adib
sumber