ExecutorService วิธีรองานทั้งหมดให้เสร็จ


200

วิธีที่ง่ายที่สุดในการรอให้ภารกิจทั้งหมดExecutorServiceเสร็จสิ้นคืออะไร งานของฉันคือการคำนวณเป็นหลักดังนั้นฉันแค่ต้องการเรียกใช้งานจำนวนมาก - หนึ่งในแต่ละหลัก ตอนนี้การตั้งค่าของฉันมีลักษณะดังนี้:

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

ComputeDTaskใช้ runnable เรื่องนี้ดูเหมือนจะดำเนินงานได้อย่างถูกต้อง แต่รหัสเกิดปัญหาในด้วยwait() IllegalMonitorStateExceptionนี่เป็นเรื่องแปลกเพราะฉันเล่นไปด้วยตัวอย่างของเล่นและดูเหมือนว่ามันจะใช้ได้

uniquePhrasesมีองค์ประกอบหลายสิบหลายหมื่น ฉันควรใช้วิธีอื่นหรือไม่? ฉันกำลังมองหาบางอย่างที่ง่ายที่สุด


1
หากคุณต้องการใช้การรอ (คำตอบบอกคุณว่าคุณไม่ต้องการ): คุณจำเป็นต้องซิงโครไนซ์กับวัตถุ (ในกรณีนี้es) เสมอเมื่อคุณต้องการรอ - การล็อคจะถูกปล่อยโดยอัตโนมัติในระหว่างที่รอ
mihi

13
วิธีที่ดีกว่าในการเริ่มต้น ThreadPool คือExecutors.newFixedThreadPool(System.getRuntime().availableProcessors());

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

[สิ่งนี้] [1] เป็นทางเลือกที่น่าสนใจ .. [1]: stackoverflow.com/questions/1250643/…
Răzvan Petruescu

1
ถ้าคุณต้องการใช้ CountDownLatch นี่คือตัวอย่างโค้ด: stackoverflow.com/a/44127101/4069305
Tuan Pham

คำตอบ:


213

วิธีที่ง่ายที่สุดคือการใช้ExecutorService.invokeAll()ซึ่งทำสิ่งที่คุณต้องการในหนึ่งซับ ในการพูดจาของคุณคุณจะต้องปรับเปลี่ยนหรือห่อComputeDTaskเพื่อนำไปใช้Callable<>ซึ่งจะทำให้คุณมีความยืดหยุ่นมากขึ้น อาจจะอยู่ในแอปของคุณมีการดำเนินงานที่มีความหมายของแต่นี่เป็นวิธีที่จะตัดมันถ้าไม่ได้ใช้Callable.call()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);

ตามที่คนอื่น ๆ ได้ชี้ให้เห็นคุณสามารถใช้เวอร์ชันไทม์เอาต์ของinvokeAll()ถ้าเหมาะสม ในตัวอย่างanswersนี้จะมีกลุ่มของFutures ซึ่งจะคืนค่าเป็นศูนย์ (ดูคำนิยามของExecutors.callable()อาจเป็นสิ่งที่คุณต้องการทำคือ refactoring เล็กน้อยเพื่อให้คุณได้รับคำตอบที่เป็นประโยชน์กลับหรืออ้างอิงถึงพื้นฐานComputeDTaskแต่ฉันสามารถ บอกจากตัวอย่างของคุณ

หากยังไม่ชัดเจนโปรดทราบว่าinvokeAll()จะไม่ส่งคืนจนกว่างานทั้งหมดจะเสร็จสิ้น (เช่นทั้งหมดFutureในanswersคอลเลกชันของคุณจะรายงาน.isDone()หากถูกถาม) สิ่งนี้จะหลีกเลี่ยงการปิดระบบด้วยตนเองทั้งหมดรอการประกาศ ฯลฯ และช่วยให้คุณสามารถนำสิ่งนี้กลับมาใช้ใหม่ได้ExecutorServiceอย่างเป็นระเบียบหากต้องการ

มีคำถามที่เกี่ยวข้องสองสามข้อเกี่ยวกับ SO:

สิ่งเหล่านี้ไม่ตรงประเด็นสำหรับคำถามของคุณ แต่พวกเขาให้สีเล็กน้อยเกี่ยวกับวิธีที่คนคิดExecutor/ ExecutorServiceควรใช้


9
สิ่งนี้เหมาะอย่างยิ่งหากคุณกำลังเพิ่มงานทั้งหมดของคุณในแบทช์และคุณเข้าสู่รายการ Callables แต่จะไม่ทำงานหากคุณโทรหา ExecutorService.submit () ในสถานการณ์การโทรกลับหรือวนรอบเหตุการณ์
Desty

2
ฉันคิดว่ามันควรค่าแก่การกล่าวถึงว่าการปิดเครื่อง () ยังควรถูกเรียกใช้เมื่อไม่ต้องการใช้ ExecutorService อีกต่อไปมิฉะนั้นเธรดจะไม่ยุติ (ยกเว้นกรณีที่ corePoolSize = 0 หรือ allowCoreThreadTimeOut = true)
John29

! ที่น่าตื่นตาตื่นใจ สิ่งที่ฉันกำลังมองหา ขอบคุณมากที่แบ่งปันคำตอบ ขอผมลองดูนะ
MohamedSanaulla

59

หากคุณต้องการที่จะรอสำหรับงานทั้งหมดจะเสร็จสมบูรณ์ให้ใช้วิธีการแทนshutdown จากนั้นตามด้วย waitawaitTermination

นอกจากนี้คุณสามารถใช้Runtime.availableProcessorsเพื่อรับจำนวนเธรดฮาร์ดแวร์เพื่อให้คุณสามารถเริ่มต้นเธรดพูลของคุณได้อย่างถูกต้อง


27
ปิด () หยุด ExecutorService จากการรับงานใหม่และปิดกระทู้งานว่าง ไม่ได้ระบุไว้เพื่อรอให้การปิดระบบเสร็จสมบูรณ์และการใช้งานใน ThreadPoolExecutor ไม่รอ
Alain O'Dea

1
@Alain - ขอบคุณ ฉันควรจะพูดถึงการรอคอยการบอกเลิก แก้ไขแล้ว.
NG

5
เกิดอะไรขึ้นถ้าเพื่อให้งานสำเร็จจะต้องกำหนดเวลางานเพิ่มเติมหรือไม่ ตัวอย่างเช่นคุณสามารถสร้างการแวะผ่านต้นไม้แบบมัลติเธรดซึ่งส่งกิ่งไปยังเธรดผู้ปฏิบัติงาน ในกรณีดังกล่าวเนื่องจาก ExecutorService ปิดตัวลงในทันทีจึงไม่สามารถรับงานที่กำหนดเวลาไว้ซ้ำได้
Brian Gordon

2
awaitTerminationต้องใช้เวลาหมดเวลาเป็นพารามิเตอร์ ในขณะที่เป็นไปได้ที่จะให้เวลา จำกัด และวางวงรอบเพื่อรอจนกว่ากระทู้ทั้งหมดจะเสร็จสิ้นฉันสงสัยว่ามีวิธีการแก้ปัญหาที่สง่างามมากขึ้น
Abhishek S

1
คุณถูกต้อง แต่ดูคำตอบนี้ - stackoverflow.com/a/1250655/263895 - คุณสามารถให้การหมดเวลานานอย่างไม่น่าเชื่อ
NG

48

ถ้ารอให้งานทั้งหมดในExecutorServiceที่จะเสร็จสิ้นไม่ได้เป็นเป้าหมายของคุณได้อย่างแม่นยำ แต่รอจนกว่าจะมีชุดเฉพาะของงานที่เสร็จสมบูรณ์แล้วคุณสามารถใช้CompletionService- ExecutorCompletionServiceโดยเฉพาะการ

ความคิดที่จะสร้างExecutorCompletionServiceการวางรูปภาพของคุณExecutor, ส่งบางส่วนจำนวนที่รู้จักกันของงานผ่านCompletionServiceแล้ววาดที่หมายเลขเดียวกันของผลที่ได้จากคิวเสร็จสิ้นโดยใช้take()(ซึ่งบล็อก) หรือpoll()(ซึ่งไม่ได้) เมื่อคุณวาดผลลัพธ์ที่คาดหวังทั้งหมดที่สอดคล้องกับงานที่คุณส่งคุณจะรู้ว่าพวกเขาทำเสร็จ

ให้ฉันบอกอีกครั้งนี้เพราะมันไม่ชัดเจนจากอินเทอร์เฟซ: คุณต้องรู้ว่าคุณใส่อะไรลงCompletionServiceไปบ้างเพื่อที่จะได้รู้ว่าต้องพยายามดึงออกมาเท่าไหร่ เรื่องนี้โดยเฉพาะอย่างยิ่งกับtake()วิธีการ: เรียกว่าครั้งหนึ่งมากเกินไปและจะป้องกันด้ายเรียกร้องของคุณจนกว่าส่งหัวข้ออื่น ๆ CompletionServiceงานอื่นไปเหมือนกัน

มีตัวอย่างบางส่วนที่แสดงวิธีการใช้CompletionServiceในหนังสือJava Concurrency ในการปฏิบัติ


นี่เป็นข้อแตกต่างที่ดีสำหรับคำตอบของฉัน - ฉันจะบอกว่าคำตอบที่ตรงไปตรงมาสำหรับคำถามคือ invokeAll (); แต่ @seh มีสิทธิ์เมื่อส่งกลุ่มของงานไปยัง ES และรอให้งานเสร็จ ... --JA
andersoj

@ om-nom-nom ขอขอบคุณสำหรับการอัปเดตลิงก์ ฉันดีใจที่เห็นว่าคำตอบยังคงมีประโยชน์
seh

1
คำตอบที่ดีฉันไม่รู้CompletionService
Vic

1
นี่เป็นวิธีการใช้หากคุณไม่ต้องการปิด ExecutorService ที่มีอยู่ แต่เพียงต้องการส่งชุดของงานและรู้ว่าเมื่อใดที่เสร็จสิ้น
ToolmakerSteve

11

หากคุณต้องการที่จะรอสำหรับการให้บริการผู้บริหารที่จะเสร็จสิ้นการดำเนินการโทรshutdown()แล้วawaitTermination (หน่วย UNITTYPE)awaitTermination(1, MINUTE)เช่น ExecutorService ไม่ได้ปิดกั้นหน้าจอของตัวเองดังนั้นคุณจึงไม่สามารถใช้งานได้waitฯลฯ


ฉันคิดว่ามันกำลังรอการประกาศ
NG

@SB - ขอบคุณ - ฉันเห็นว่าความจำของฉันผิดพลาด! ฉันได้อัปเดตชื่อและเพิ่มลิงค์เพื่อให้แน่ใจ
mdma

หากต้องการรอ "ตลอดไป" ใช้เหมือนawaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); stackoverflow.com/a/1250655/32453
rogerdpack

ฉันคิดว่านี่เป็นวิธีที่ง่ายที่สุด
Shervin Asgari

1
@MosheElisha คุณแน่ใจหรือไม่ docs.oracle.com/javase/8/docs/api/java/util/concurrent/...กล่าวริเริ่มการปิดระบบที่เป็นระเบียบเรียบร้อยในงานที่ส่งก่อนหน้านี้จะดำเนินการ แต่ไม่มีงานใหม่จะได้รับการยอมรับ
Jaime Hablutzel

7

คุณสามารถรอให้งานเสร็จในช่วงเวลาหนึ่ง:

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

หรือคุณอาจจะใช้ExecutorService ส่ง ( Runnable ) และรวบรวมวัตถุในอนาคตที่ส่งคืนและเรียกใช้get ()ในแต่ละรายการเพื่อรอให้เสร็จสิ้น

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

InterruptExceptionเป็นสิ่งสำคัญอย่างยิ่งในการจัดการอย่างถูกต้อง เป็นสิ่งที่ช่วยให้คุณหรือผู้ใช้ไลบรารีของคุณยุติกระบวนการที่ยาวนานได้อย่างปลอดภัย


6

เพียงแค่ใช้

latch = new CountDownLatch(noThreads)

ในแต่ละกระทู้

latch.countDown();

และเป็นอุปสรรค

latch.await();

6

สาเหตุหลักสำหรับIllegalMonitorStateException :

โยนเพื่อระบุว่าเธรดพยายามรอจอภาพของวัตถุหรือแจ้งเตือนเธรดอื่น ๆ ที่รอบนจอภาพของวัตถุโดยไม่ได้เป็นเจ้าของจอภาพที่ระบุ

จากรหัสของคุณคุณเพิ่งเรียกว่า wait () บน ExecutorService โดยไม่ต้องเป็นเจ้าของล็อค

รหัสด้านล่างจะแก้ไข IllegalMonitorStateException

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

ExecutorServiceทำตามอย่างใดอย่างหนึ่งดังต่อไปนี้วิธีการรอให้เสร็จสิ้นของงานทั้งหมดที่ได้รับการส่งไปยัง

  1. วนซ้ำทุกFutureภารกิจจากsubmitเปิดExecutorServiceและตรวจสอบสถานะด้วยการบล็อคการโทรget()บนFutureวัตถุ

  2. ใช้invokeAllเปิดExecutorService

  3. ใช้CountDownLatch

  4. ใช้ForkJoinPoolหรือnewWorkStealingPoolของExecutors(ตั้งแต่ java 8)

  5. ปิดพูลตามที่แนะนำในหน้าเอกสารของ 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();
    }

    หากคุณต้องการรอให้งานทั้งหมดเสร็จสิ้นอย่างสง่างามเมื่อคุณใช้ตัวเลือกที่ 5 แทนตัวเลือกที่ 1 ถึง 4 ให้เปลี่ยน

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

    ถึง

    while(condition)ซึ่งการตรวจสอบสำหรับทุก 1 นาที


6

คุณสามารถใช้ExecutorService.invokeAllวิธีการมันจะทำงานทั้งหมดและรอจนกว่ากระทู้ทั้งหมดเสร็จงานของพวกเขา

นี่คือjavadoc ที่สมบูรณ์

คุณยังสามารถใช้วิธีการนี้มากเกินไปเพื่อระบุการหมดเวลา

นี่คือตัวอย่างรหัสด้วย 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

ฉันยังมีสถานการณ์ที่ฉันมีชุดเอกสารที่จะรวบรวมข้อมูล ฉันเริ่มต้นด้วยเอกสาร "seed" เริ่มต้นที่ควรประมวลผลเอกสารนั้นมีลิงก์ไปยังเอกสารอื่นที่ควรประมวลผลและอื่น ๆ

ในโปรแกรมหลักของฉันฉันต้องการเขียนสิ่งต่อไปนี้ซึ่งCrawlerควบคุมกลุ่มของเธรด

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

สถานการณ์เดียวกันจะเกิดขึ้นถ้าฉันต้องการนำทางต้นไม้; ฉันจะปรากฏในโหนดรูทหน่วยประมวลผลสำหรับแต่ละโหนดจะเพิ่มลูกลงในคิวตามความจำเป็นและกลุ่มของเธรดจะประมวลผลโหนดทั้งหมดในทรีจนกว่าจะไม่มีอีกต่อไป

ฉันไม่พบอะไรเลยใน JVM ที่ฉันคิดว่าน่าแปลกใจเล็กน้อย ดังนั้นฉันจึงเขียนชั้นเรียนที่หนึ่งที่สามารถใช้โดยตรงหรือประเภทรองเพื่อเพิ่มวิธีการที่เหมาะสมสำหรับโดเมนเช่นThreadPool schedule(Document)หวังว่ามันจะช่วย!

ThreadPool Javadoc | Maven


Doc Link ตายแล้ว
Manti_Core

@Manti_Core - ขอบคุณอัปเดตแล้ว
เอเดรียนสมิ ธ

2

invokeAllเพิ่มหัวข้อทั้งหมดในการเก็บรวบรวมและส่งไปใช้ หากคุณสามารถใช้invokeAllวิธีการได้ExecutorServiceJVM จะไม่ดำเนินการไปยังบรรทัดถัดไปจนกว่าเธรดทั้งหมดจะเสร็จสมบูรณ์

นี่คือตัวอย่างที่ดี: เรียกใช้ทั้งหมดผ่าน ExecutorService


1

ส่งงานของคุณไปที่Runnerแล้วรอเรียกเมธอดwaitTillDone ()ดังนี้:

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

หากต้องการใช้เพิ่มการพึ่งพา gradle / Maven นี้: 'com.github.matejtymes:javafixes:1.0'

สำหรับรายละเอียดเพิ่มเติมดูที่นี่: https://github.com/MatejTymes/JavaFixesหรือที่นี่: http://matejtymes.blogspot.com/2016/04/executor-that-notifies-you-when-task.html



0

ฉันจะรอให้ผู้ดำเนินการสิ้นสุดเวลาที่กำหนดซึ่งคุณคิดว่าเหมาะสมสำหรับงานที่ต้องทำให้เสร็จ

 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

ฟังดูเหมือนคุณต้องการForkJoinPoolและใช้พูลส่วนกลางเพื่อดำเนินงาน

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

ความงามอยู่ในpool.awaitQuiescenceที่วิธีการที่จะป้องกันการใช้ด้ายที่โทรมาในการดำเนินงานและจากนั้นกลับมาเมื่อมันเป็นจริงๆที่ว่างเปล่า

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.