ExecutorService, làm thế nào để đợi tất cả các nhiệm vụ hoàn thành


196

Cách đơn giản nhất để chờ đợi cho tất cả các nhiệm vụ ExecutorServicehoàn thành là gì? Nhiệm vụ của tôi chủ yếu là tính toán, vì vậy tôi chỉ muốn điều hành một số lượng lớn công việc - một công việc trên mỗi lõi. Ngay bây giờ thiết lập của tôi trông như thế này:

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

ComputeDTaskthực hiện runnable. Điều này dường như để thực thi các nhiệm vụ chính xác, nhưng mã gặp sự cố wait()với IllegalMonitorStateException. Điều này thật kỳ lạ, bởi vì tôi đã chơi xung quanh với một số ví dụ về đồ chơi và nó dường như hoạt động.

uniquePhraseschứa vài chục ngàn yếu tố. Tôi có nên sử dụng một phương pháp khác? Tôi đang tìm kiếm một cái gì đó đơn giản nhất có thể


1
nếu bạn muốn sử dụng chờ (câu trả lời cho bạn biết là bạn không): Bạn luôn cần đồng bộ hóa trên đối tượng (trong trường hợp này es) khi bạn muốn đợi nó - khóa sẽ tự động được giải phóng trong khi chờ đợi
mihi

13
một cách tốt hơn để khởi tạo ThreadPool làExecutors.newFixedThreadPool(System.getRuntime().availableProcessors());

7
Runtime.getR.78 (). AvailableProcessors ();
Vik Gamov

[Đây] [1] là một thay thế thú vị .. [1]: stackoverflow.com/questions/1250643/iêu
Răzvan Petruescu

1
nếu bạn muốn sử dụng CountDownLatch, đây là mã ví dụ: stackoverflow.com/a/44127101/4069305
Tuấn Phạm

Câu trả lời:


213

Cách tiếp cận đơn giản nhất là sử dụng ExecutorService.invokeAll()cái mà bạn muốn trong một lớp lót. Theo cách nói của bạn, bạn sẽ cần sửa đổi hoặc bọc ComputeDTaskđể thực hiện Callable<>, điều này có thể giúp bạn linh hoạt hơn một chút. Có thể trong ứng dụng của bạn có một triển khai có ý nghĩa Callable.call(), nhưng đây là một cách để bọc nó nếu không sử dụng 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);

Như những người khác đã chỉ ra, bạn có thể sử dụng phiên bản hết thời gian invokeAll()nếu thích hợp. Trong ví dụ này, answerssẽ chứa một bó Futures sẽ trả về null (xem định nghĩa của Executors.callable(). Có lẽ điều bạn muốn làm là một phép tái cấu trúc nhẹ để bạn có thể lấy lại câu trả lời hữu ích hoặc tham chiếu đến phần bên dưới ComputeDTask, nhưng tôi có thể Tôi sẽ kể từ ví dụ của bạn.

Nếu nó không rõ ràng, lưu ý rằng invokeAll()sẽ không trở lại cho đến khi tất cả các nhiệm vụ được hoàn thành. (nghĩa là tất cả các Futures trong answersbộ sưu tập của bạn sẽ báo cáo .isDone()nếu được hỏi.) Điều này tránh tất cả việc tắt thủ công, awaitTermination, v.v ... và cho phép bạn sử dụng lại ExecutorServicegọn gàng trong nhiều chu kỳ, nếu muốn.

Có một vài câu hỏi liên quan về SO:

Không ai trong số này đúng về câu hỏi của bạn, nhưng chúng cung cấp một chút màu sắc về cách mọi người nghĩ Executor/ ExecutorServicenên sử dụng.


9
Điều này là hoàn hảo nếu bạn thêm tất cả các công việc của mình theo một đợt và bạn treo vào danh sách các Callables, nhưng nó sẽ không hoạt động nếu bạn gọi ExecutorService.submit () trong tình huống gọi lại hoặc vòng lặp sự kiện.
Desty

2
Tôi nghĩ rằng đáng để đề cập rằng tắt máy () vẫn nên được gọi khi ExecutorService không còn cần thiết nữa, nếu không các luồng sẽ không bao giờ chấm dứt (trừ trường hợp corePoolSize = 0 hoặc allowCoreThreadTimeOut = true).
John29

kinh ngạc! Chỉ cần những gì tôi đang tìm kiếm. Cảm ơn rất nhiều vì đã chia sẻ câu trả lời. Hãy để tôi thử điều này.
MohamedSanaulla

59

Nếu bạn muốn đợi tất cả các tác vụ hoàn thành, hãy sử dụng shutdownphương thức thay vì wait. Sau đó làm theo nó với awaitTermination.

Ngoài ra, bạn có thể sử dụng Runtime.availableProcessorsđể lấy số lượng luồng xử lý phần cứng để bạn có thể khởi tạo chính xác luồng của mình.


27
shutdown () ngăn ExecutorService chấp nhận các tác vụ mới và đóng các luồng công nhân nhàn rỗi. Nó không được chỉ định để chờ tắt máy hoàn tất và việc triển khai trong ThreadPoolExecutor không chờ đợi.
Alain O'Dea

1
@ Alain - cảm ơn. Tôi nên đã đề cập đến awaitTermination. Đã sửa.
NG.

5
Điều gì sẽ xảy ra nếu để một nhiệm vụ hoàn thành nó phải lên lịch cho các nhiệm vụ tiếp theo? Ví dụ, bạn có thể thực hiện một giao dịch cây đa luồng, trao các nhánh cho các luồng công nhân. Trong trường hợp đó, vì ExecutorService bị tắt ngay lập tức nên nó không chấp nhận bất kỳ công việc được lên lịch đệ quy nào.
Brian Gordon

2
awaitTerminationyêu cầu thời gian chờ là một tham số. Mặc dù có thể cung cấp thời gian hữu hạn và đặt một vòng lặp xung quanh nó để đợi cho đến khi tất cả các luồng kết thúc, tôi tự hỏi liệu có một giải pháp thanh lịch hơn.
Abhishek S

1
Bạn đã đúng, nhưng hãy xem câu trả lời này - stackoverflow.com/a/1250655/263895 - bạn luôn có thể cho nó thời gian chờ cực kỳ dài
NG.

48

Nếu việc chờ đợi tất cả các nhiệm vụ ExecutorServicehoàn thành không chính xác là mục tiêu của bạn, mà là chờ cho đến khi một loạt nhiệm vụ cụ thể hoàn thành, bạn có thể sử dụng một CompletionService- cụ thể là, một ExecutorCompletionService.

Ý tưởng là tạo ra một ExecutorCompletionServicegói của bạn Executor, gửi một số nhiệm vụ đã biết thông qua CompletionService, sau đó rút ra cùng một số kết quả từ hàng đợi hoàn thành bằng cách sử dụng take()(khối nào) hoặc poll()(không có). Khi bạn đã rút ra tất cả các kết quả mong đợi tương ứng với các nhiệm vụ bạn đã gửi, bạn biết rằng tất cả đều được thực hiện.

Hãy để tôi nói điều này thêm một lần nữa, bởi vì nó không rõ ràng từ giao diện: Bạn phải biết có bao nhiêu thứ bạn đưa vào CompletionServiceđể biết có bao nhiêu thứ để thử rút ra. Điều này đặc biệt quan trọng với take()phương thức: gọi nó một lần quá nhiều và nó sẽ chặn luồng gọi của bạn cho đến khi một số luồng khác gửi công việc khác đến cùng CompletionService.

một số ví dụ cho thấy cách sử dụngCompletionService trong cuốn sách Java Concurrency in Practice .


Đây là một phản biện tốt cho câu trả lời của tôi - Tôi muốn nói câu trả lời đơn giản cho câu hỏi là invoke ALL (); nhưng @seh có quyền khi gửi các nhóm công việc cho ES và chờ họ hoàn thành ... --JA
andersoj

@ om-nom-nom, cảm ơn bạn đã cập nhật các liên kết. Tôi vui mừng khi thấy rằng câu trả lời vẫn hữu ích.
seh

1
Câu trả lời hay, tôi không biếtCompletionService
Vic

1
Đây là cách tiếp cận để sử dụng, nếu bạn không muốn tắt ExecutorService hiện tại, nhưng chỉ muốn gửi một loạt nhiệm vụ và biết khi nào tất cả hoàn thành.
ToolmakerSteve

11

Nếu bạn muốn đợi dịch vụ thực thi kết thúc thực thi, hãy gọi shutdown()và sau đó, awaitTermination (units, unitType) , vd awaitTermination(1, MINUTE). ExecutorService không chặn trên màn hình của chính nó, vì vậy bạn không thể sử dụng, waitv.v.


Tôi nghĩ rằng nó đang chờ đợi.
NG.

@SB - Cảm ơn - Tôi thấy trí nhớ của tôi rất dễ đọc! Tôi đã cập nhật tên và thêm một liên kết để chắc chắn.
mdma

Để đợi "mãi mãi", hãy sử dụng nó như awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); stackoverflow.com/a/1250655/32453
rogerdpack

Tôi nghĩ rằng đây là cách tiếp cận dễ dàng nhất
Shervin Asgari

1
@MosheElisha, bạn có chắc không?. docs.oracle.com/javase/8/docs/api/java/util/concản/ cảm nói Bắt đầu tắt máy theo thứ tự trong đó các nhiệm vụ được gửi trước đó được thực thi, nhưng không có nhiệm vụ mới nào được chấp nhận.
Jaime Hablutzel

7

Bạn có thể đợi công việc hoàn thành trong một khoảng thời gian nhất định:

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

Hoặc bạn có thể sử dụng ExecutorService . gửi ( Runnable ) và thu thập các đối tượng Tương lai mà nó trả về và gọi get () lần lượt để chờ chúng kết thúc.

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

Interrupttedception là cực kỳ quan trọng để xử lý đúng. Đó là những gì cho phép bạn hoặc người dùng thư viện của bạn chấm dứt một quá trình dài một cách an toàn.


6

Chỉ dùng

latch = new CountDownLatch(noThreads)

Trong mỗi chủ đề

latch.countDown();

và như là rào cản

latch.await();

6

Nguyên nhân gốc rễ cho IllegalMonitorStateException :

Ném để chỉ ra rằng một luồng đã cố đợi trên màn hình của đối tượng hoặc thông báo cho các luồng khác đang chờ trên màn hình của đối tượng mà không sở hữu màn hình được chỉ định.

Từ mã của bạn, bạn vừa gọi Wait () trên ExecutorService mà không cần sở hữu khóa.

Mã dưới đây sẽ sửa IllegalMonitorStateException

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

Thực hiện theo một trong các cách tiếp cận dưới đây để chờ hoàn thành tất cả các nhiệm vụ đã được gửi tới ExecutorService.

  1. Lặp lại tất cả các Futuretác vụ từ submittrên ExecutorServicevà kiểm tra trạng thái bằng cách chặn cuộc gọi get()trên Futuređối tượng

  2. Sử dụng invoke ALL trênExecutorService

  3. Sử dụng CountDownLatch

  4. Sử dụng ForkJoinPool hoặc newWorkStealsPool của Executors(kể từ java 8)

  5. Tắt máy theo khuyến nghị trong trang tài liệu 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();
    }

    Nếu bạn muốn duyên dáng chờ đợi tất cả các nhiệm vụ hoàn thành khi bạn đang sử dụng tùy chọn 5 thay vì tùy chọn 1 đến 4, hãy thay đổi

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

    đến

    một while(condition)kiểm tra cứ sau 1 phút


6

Bạn có thể sử dụng ExecutorService.invokeAllphương thức, Nó sẽ thực thi tất cả các tác vụ và đợi cho đến khi tất cả các luồng hoàn thành nhiệm vụ của chúng.

Đây là javadoc hoàn chỉnh

Bạn cũng có thể sử dụng phiên bản quá tải của phương thức này để chỉ định thời gian chờ.

Đây là mã mẫu với 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

Tôi cũng có một tình huống là tôi có một bộ tài liệu được thu thập thông tin. Tôi bắt đầu với một tài liệu "hạt giống" ban đầu cần được xử lý, tài liệu đó chứa các liên kết đến các tài liệu khác cũng cần được xử lý, v.v.

Trong chương trình chính của tôi, tôi chỉ muốn viết một cái gì đó như sau, nơi Crawlerđiều khiển một loạt các chủ đề.

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

Tình huống tương tự sẽ xảy ra nếu tôi muốn điều hướng một cái cây; Tôi sẽ bật trong nút gốc, bộ xử lý cho mỗi nút sẽ thêm con vào hàng đợi khi cần thiết và một loạt các luồng sẽ xử lý tất cả các nút trong cây, cho đến khi không còn nữa.

Tôi không thể tìm thấy bất cứ điều gì trong JVM mà tôi nghĩ là hơi ngạc nhiên. Vì vậy, tôi đã viết một lớp ThreadPoolmà người ta có thể sử dụng trực tiếp hoặc lớp con để thêm các phương thức phù hợp với miền, ví dụ schedule(Document). Hy vọng nó giúp!

ThreadPool Javadoc | Maven


Liên kết tài liệu đã chết
Manti_Core

@Manti_Core - cảm ơn, đã cập nhật.
Adrian Smith

2

Thêm tất cả các chủ đề trong bộ sưu tập và gửi nó bằng cách sử dụng invokeAll. Nếu bạn có thể sử dụng invokeAllphương thức của ExecutorService, JVM sẽ không chuyển sang dòng tiếp theo cho đến khi tất cả các luồng hoàn thành.

Đây là một ví dụ điển hình: invoke ALL thông qua ExecutorService


1

Gửi các nhiệm vụ của bạn vào Người chạy và sau đó chờ gọi phương thức WaitTillDone () như thế này:

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

Để sử dụng nó thêm phụ thuộc gradle / maven này: 'com.github.matejtymes:javafixes:1.0'

Để biết thêm chi tiết, hãy xem tại đây: https://github.com/MatejTymes/JavaFixes hoặc tại đây: http://matejtymes.blogspot.com/2016/04/executor-that-notifying-you-when-task.html



0

Tôi sẽ chỉ chờ người thực thi chấm dứt với thời gian chờ đã chỉ định mà bạn nghĩ rằng nó phù hợp với các nhiệm vụ cần hoàn thành.

 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

Âm thanh như bạn cần ForkJoinPoolvà sử dụng nhóm toàn cầu để thực hiện các nhiệm vụ.

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

Vẻ đẹp nằm ở pool.awaitQuiescencechỗ phương thức sẽ chặn sử dụng luồng của trình gọi để thực thi các tác vụ của nó và sau đó quay lại khi nó thực sự trống.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.