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. Futuresubmit에서 모든 작업을 반복 하고 객체 에 대한 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.