ExecutorService, 모든 작업이 완료 될 때까지 기다리는 방법


196

모든 작업이 ExecutorService완료 될 때까지 기다리는 가장 간단한 방법은 무엇입니까 ? 내 작업은 주로 계산 작업이므로 각 코어마다 하나씩 많은 수의 작업을 실행하고 싶습니다. 지금 내 설정은 다음과 같습니다.

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

ComputeDTask실행 가능을 구현합니다. 이 올바르게 작업을 실행하는 것처럼 보이지만 코드에 충돌 wait()과 함께 IllegalMonitorStateException. 나는 장난감 예제를 가지고 놀았고 작동하는 것처럼 보였기 때문에 이상합니다.

uniquePhrases수만 개의 요소를 포함합니다. 다른 방법을 사용해야합니까? 가능한 한 간단한 것을 찾고 있습니다


1
당신이 대기 (답변이 당신에게 그렇게하지)를 사용하기를 원한다면 : 당신은 항상 (이 경우 개체에 동기화 할 필요가 es- 기다리는 동안 잠금이 자동으로 해제됩니다 당신이 그것을 기다려야 할 때)
— mihi

13
ThreadPool을 초기화하는 더 좋은 방법은Executors.newFixedThreadPool(System.getRuntime().availableProcessors());

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

[This] [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()적절한 경우 시간 초과 버전을 사용할 수 있습니다 . 이 예에서는 null을 반환하는 s answers뭉치를 포함합니다 Future(의 정의 참조 Executors.callable(). 아마 당신이하고 싶은 것은 약간의 리팩토링이므로 유용한 답변을 얻거나 기본에 대한 참조를 ComputeDTask얻을 수 있지만 당신의 모범에서 말하지 마십시오.

확실 invokeAll()하지 않으면 모든 작업이 완료 될 때까지 반환되지 않습니다. (즉, 컬렉션 Future의 모든 s가 요청 answers되면보고 .isDone()합니다.) 이렇게하면 모든 수동 종료, awaitTermination 등을 피할 수 있으며 ExecutorService원하는 경우 여러주기 동안 깔끔하게 재사용 할 수 있습니다 .

SO에 대한 몇 가지 관련 질문이 있습니다.

이 중 어느 것도 엄격에 포인트 질문에 대한 없지만, 그들은 사람들이 생각하는 방법에 대한 색의 약간 제공 할 Executor/가 ExecutorService사용되어야한다을.


9
일괄 작업으로 모든 작업을 추가하고 Callables 목록에 매달리면 완벽하지만 콜백 또는 이벤트 루프 상황에서 ExecutorService.submit ()을 호출하면 작동하지 않습니다.
— Desty

2
ExecutorService가 더 이상 필요하지 않은 경우 shutdown ()을 여전히 호출해야한다고 언급 할 가치가 있다고 생각합니다. 그렇지 않으면 스레드가 종료되지 않습니다 (corePoolSize = 0 또는 allowCoreThreadTimeOut = true 인 경우 제외).
— John29

놀랄 만한! 내가 찾던 것. 답변을 공유해 주셔서 감사합니다. 이것을 시험해 보자.
— MohamedSanaulla

59

모든 작업이 완료 될 때까지 기다리려면 shutdown대신 방법을 사용하십시오 wait. 그런 다음로 수행하십시오 awaitTermination.

또한 Runtime.availableProcessors스레드 풀 수를 올바르게 초기화 할 수 있도록 하드웨어 스레드 수를 얻는 데 사용할 수 있습니다 .


27
shutdown ()은 ExecutorService가 새 작업을받지 못하게하고 유휴 작업자 스레드를 닫습니다. 종료가 완료 될 때까지 대기하도록 지정되지 않았으며 ThreadPoolExecutor의 구현이 대기하지 않습니다.
— Alain O'Dea

1
@Alain-감사합니다. awaitTermination을 언급 했어야합니다. 결정된.
— NG.

5
작업을 완료하기 위해 추가 작업을 예약해야하는 경우 어떻게해야합니까? 예를 들어 분기를 작업자 스레드로 전달하는 멀티 스레드 트리 탐색을 만들 수 있습니다. 이 경우 ExecutorService가 즉시 종료되므로 재귀 적으로 예약 된 작업을 수락하지 못합니다.
— 브라이언 고든

2
awaitTermination매개 변수로 시간 종료 시간이 필요합니다. 유한 한 시간을 제공하고 그 주위에 루프를 배치하여 모든 스레드가 완료 될 때까지 기다릴 수는 있지만 더 우아한 솔루션이 있는지 궁금합니다.
— Abhishek S

1
당신이 바로,하지만이 답변을 참조 - stackoverflow.com/a/1250655/263895을 - 당신은 항상 그것을 믿을 수 없을만큼 긴 시간 제한을 줄 수
— NG를.

48

ExecutorService완료 할 때까지의 모든 작업을 기다리는 것이 정확히 목표가 아니라 특정 작업 이 완료 될 때까지 기다리는 경우, 특히CompletionService -를 사용할 수 있습니다 ExecutorCompletionService.

아이디어는이 만드는 것입니다 ExecutorCompletionService귀하의 포장 Executor, 제출 일부 알려진 번호를 관통 작업을 CompletionService한 후, 그 그릴 같은 번호 로부터 결과의 완성 대기열 중 하나를 사용하여 take()(이 블록) 또는 poll()(하지 않음). 제출 한 작업에 해당하는 모든 예상 결과를 가져 오면 모두 완료된 것입니다.

인터페이스에서 명확하지 않기 때문에 이것을 다시 한 번 언급하겠습니다 CompletionService. 이것은 특히 take()메소드 와 관련이 있습니다. 한 번 너무 많이 호출하면 다른 스레드가 다른 작업을 동일한 작업에 제출 할 때까지 호출 스레드를 차단합니다 CompletionService.

Java Concurrency in Practice 책에서 사용하는 방법을 보여주는 몇 가지 예가CompletionService 있습니다 .


이것은 내 대답에 대한 좋은 반론입니다. 질문에 대한 직접적인 대답은 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 아스 가리

1
@MosheElisha, 확실합니까?. docs.oracle.com/javase/8/docs/api/java/util/concurrent/… 말한다 이전에 제출 된 작업이 실행되는 순서대로 종료를 시작하지만 새 작업은 수락되지 않습니다.
— Jaime Hablutzel 2011

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 )을 제출 하고 반환 하는 Future 객체를 수집하고 각각 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);
   }
}

올바르게 처리하려면 InterruptedException 이 매우 중요합니다. 그것은 당신이나 당신의 라이브러리 사용자가 긴 프로세스를 안전하게 종료 할 수있게합니다.


6

그냥 사용

latch = new CountDownLatch(noThreads)

각 실에서

latch.countDown();

그리고 장벽으로

latch.await();

6

IllegalMonitorStateException의 근본 원인 :

스레드가 객체의 모니터에서 대기를 시도했거나 지정된 모니터를 소유하지 않고 객체의 모니터에서 대기중인 다른 스레드에게 알리려고 시도했음을 나타냅니다.

코드에서 잠금을 소유하지 않고 ExecutorService에서 wait ()를 호출했습니다.

아래 코드는 수정됩니다 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(자바 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();
    }

    옵션 1-4 대신 옵션 5를 사용할 때 모든 작업이 완료 될 때까지 기다리려면 변경하십시오.

    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

또한 크롤링 할 문서 세트가있는 상황도 있습니다. 먼저 처리해야 할 초기 "씨앗"문서로 시작하고 해당 문서에는 처리해야하는 다른 문서에 대한 링크가 포함되어 있습니다.

내 주요 프로그램에서 나는 단지 Crawler많은 스레드를 제어하는 다음과 같은 것을 작성하고 싶습니다 .

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

트리를 탐색하려는 경우에도 동일한 상황이 발생합니다. 루트 노드에 팝업하고 각 노드의 프로세서가 필요에 따라 자식을 대기열에 추가하고 더 이상 없을 때까지 많은 스레드가 트리의 모든 노드를 처리합니다.

JVM에서 조금 놀랍다 고 생각되는 것을 찾을 수 없었습니다. 그래서 ThreadPool직접 또는 서브 클래스를 사용하여 도메인에 적합한 메소드를 추가 할 수 있는 클래스 를 작성했습니다 ( 예 :) schedule(Document). 그것이 도움이되기를 바랍니다!

ThreadPool Javadoc | 메이븐


문서 링크가 종료되었습니다
— Manti_Core

@Manti_Core-감사합니다.
— Adrian Smith

2

콜렉션에있는 모든 스레드를 추가하고를 사용하여 제출하십시오 invokeAll. 의 invokeAll방법을 사용할 수 있으면 ExecutorService모든 스레드가 완료 될 때까지 JVM이 다음 줄로 진행되지 않습니다.

여기 좋은 예가 있습니다 : ExecutorService를 통한 invokeAll


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.