đợi cho đến khi tất cả các chuỗi hoàn thành công việc của họ trong java


91

Tôi đang viết một ứng dụng có 5 luồng lấy một số thông tin từ web đồng thời và điền vào 5 trường khác nhau trong một lớp đệm.
Tôi cần xác thực dữ liệu bộ đệm và lưu trữ nó trong cơ sở dữ liệu khi tất cả các luồng hoàn thành công việc của chúng.
Làm thế nào tôi có thể làm điều này (nhận được cảnh báo khi tất cả các chủ đề hoàn thành công việc của họ)?


4
Thread.join là một cách đồng bộ hóa Java ở mức khá thấp để giải quyết vấn đề. Hơn nữa nó có vấn đề vì các Chủ đề API là thiếu sót: bạn không thể biết liệu tham gia hoàn thành successfuly hay không (xem Java Concurrency in Practice ). Mức độ trừu tượng cao hơn, như sử dụng CountDownLatch có thể thích hợp hơn và sẽ trông tự nhiên hơn đối với các lập trình viên không bị "mắc kẹt" trong tư duy theo phong cách Java. Đừng tranh luận với tôi, hãy tranh luận với Doug Lea; )
Cedric Martin

Câu trả lời:


119

Cách tiếp cận mà tôi thực hiện là sử dụng ExecutorService để quản lý các nhóm luồng.

ExecutorService es = Executors.newCachedThreadPool();
for(int i=0;i<5;i++)
    es.execute(new Runnable() { /*  your task */ });
es.shutdown();
boolean finished = es.awaitTermination(1, TimeUnit.MINUTES);
// all tasks have finished or the time has been reached.

7
@Leonid đó chính xác là những gì shutdown () làm.
Peter Lawrey

3
while(!es.awaitTermination(1, TimeUnit.MINUTES));
Aquarius Power

3
@AquariusPower Bạn có thể yêu cầu nó đợi lâu hơn hoặc mãi mãi.
Peter Lawrey

1
oh .. tôi hiểu rồi; vì vậy tôi đã thêm một thông báo trong vòng lặp nói rằng nó đang đợi tất cả các chuỗi kết thúc; cảm ơn!
Aquarius Power

1
@PeterLawrey, Có cần gọi es.shutdown();không? điều gì sẽ xảy ra nếu tôi viết một mã trong đó tôi thực thi một luồng bằng cách sử dụng es.execute(runnableObj_ZipMaking);trong trykhối và trong đó finallytôi đã gọi boolean finshed = es.awaitTermination(10, TimeUnit.MINUTES);. Vì vậy, tôi cho rằng điều này nên đợi cho đến khi tất cả các luồng hoàn thành công việc của chúng hoặc thời gian chờ xảy ra (bất cứ điều gì xảy ra trước), Giả định của tôi có đúng không? hay cuộc gọi đến shutdown()là bắt buộc?
Amogh

52

Bạn có thể joinđến các chủ đề. Các khối nối cho đến khi chuỗi hoàn thành.

for (Thread thread : threads) {
    thread.join();
}

Lưu ý rằng joinném một InterruptedException. Bạn sẽ phải quyết định phải làm gì nếu điều đó xảy ra (ví dụ: cố gắng hủy các luồng khác để ngăn chặn công việc không cần thiết được thực hiện).


1
Các luồng này chạy song song hay tuần tự với nhau?
James Webster

5
@JamesWebster: Song song.
RYN

4
@James Webster: Câu lệnh t.join();có nghĩa là luồng hiện tại sẽ chặn cho đến khi luồng tkết thúc. Nó không ảnh hưởng đến chủ đề t.
Mark Byers

1
Cảm ơn. =] Đã học paralellism tại uni, nhưng đó là điều duy nhất tôi phải vật lộn để học! Rất may tôi không cần phải sử dụng nó ngay bây giờ hoặc nhiều khi tôi làm điều đó không phải là quá phức tạp hoặc có những không chia sẻ nguồn lực và ngăn chặn là không quan trọng
James Webster

1
@ 4r1y4n Việc mã được cung cấp có thực sự song song hay không tùy thuộc vào những gì bạn đang cố gắng thực hiện với nó và liên quan nhiều hơn đến việc tổng hợp dữ liệu trải rộng trong các bộ sưu tập bằng cách sử dụng các chuỗi được kết hợp. Bạn đang tham gia chuỗi, có thể có nghĩa là dữ liệu "tham gia". Ngoài ra, song song KHÔNG nhất thiết có nghĩa là đồng thời. Điều đó phụ thuộc vào CPU. Rất có thể các luồng đang chạy song song, nhưng việc tính toán đang diễn ra theo bất kỳ thứ tự nào được xác định bởi CPU bên dưới.

22

Hãy xem các giải pháp khác nhau.

  1. join()API đã được giới thiệu trong các phiên bản đầu tiên của Java. Một số lựa chọn thay thế tốt có sẵn với gói đồng thời này kể từ bản phát hành JDK 1.5.

  2. ExecutorService # invokeAll ()

    Thực thi các nhiệm vụ đã cho, trả về danh sách các Hợp đồng tương lai giữ trạng thái và kết quả của họ khi mọi thứ hoàn thành.

    Tham khảo câu hỏi SE liên quan này để biết ví dụ về mã:

    Làm thế nào để sử dụng invokeAll () để cho phép tất cả nhóm luồng thực hiện nhiệm vụ của chúng?

  3. CountDownLatch

    Hỗ trợ đồng bộ hóa cho phép một hoặc nhiều luồng đợi cho đến khi một tập hợp các thao tác được thực hiện trong các luồng khác hoàn tất.

    Một CountDownLatch được khởi tạo với một số lượng nhất định. Các phương thức await sẽ chặn cho đến khi số lượng hiện tại bằng 0 do các lệnh gọi của countDown()phương thức, sau đó tất cả các luồng chờ được giải phóng và mọi lệnh gọi tiếp theo của await sẽ trả về ngay lập tức. Đây là hiện tượng một lần - không thể đặt lại số đếm. Nếu bạn cần một phiên bản đặt lại số lượng, hãy xem xét sử dụng CyclicBarrier .

    Tham khảo câu hỏi này để biết cách sử dụng CountDownLatch

    Làm thế nào để chờ một chủ đề sinh ra chủ đề đó?

  4. ForkJoinPool hoặc newWorkStealingPool () trong Executor

  5. Lặp lại tất cả các đối tượng Tương lai được tạo sau khi gửi đếnExecutorService


11

Ngoài Thread.join()đề xuất của những người khác, java 5 đã giới thiệu khung thực thi. Ở đó bạn không làm việc với Threadcác đối tượng. Thay vào đó, bạn gửi Callablehoặc Runnablecác đối tượng của mình cho một người thực thi. Có một người thực thi đặc biệt có nghĩa là thực hiện nhiều nhiệm vụ và trả lại kết quả của chúng không theo thứ tự. Đó là ExecutorCompletionService:

ExecutorCompletionService executor;
for (..) {
    executor.submit(Executors.callable(yourRunnable));
}

Sau đó, bạn có thể gọi liên tục take()cho đến khi không còn Future<?>đối tượng nào để trả lại, có nghĩa là tất cả chúng đã hoàn thành.


Một điều khác có thể có liên quan, tùy thuộc vào kịch bản của bạn là CyclicBarrier.

Một công cụ hỗ trợ đồng bộ hóa cho phép một tập hợp tất cả các chủ đề chờ nhau đạt đến một điểm rào cản chung. CyclicBarriers hữu ích trong các chương trình liên quan đến một nhóm các luồng có kích thước cố định mà đôi khi phải đợi nhau. Rào cản được gọi là tuần hoàn vì nó có thể được sử dụng lại sau khi các luồng chờ được giải phóng.


Điều này đã kết thúc, nhưng tôi vẫn sẽ thực hiện một vài điều chỉnh. executor.submittrả về a Future<?>. Tôi sẽ thêm những tương lai này vào một danh sách, và sau đó lặp lại danh sách đang gọi getcho mỗi tương lai.
Ray

Ngoài ra, bạn có thể nhanh chóng sử dụng một constructor Executors, ví dụ Executors.newCachedThreadPool(hoặc tương tự)
Ray

10

Một khả năng khác là CountDownLatchđối tượng, hữu ích cho các tình huống đơn giản: vì bạn biết trước số lượng luồng, bạn khởi tạo nó với số lượng có liên quan và chuyển tham chiếu của đối tượng cho mỗi luồng.
Sau khi hoàn thành nhiệm vụ của nó, mỗi luồng gọi CountDownLatch.countDown()sẽ giảm bộ đếm bên trong. Chuỗi chính, sau khi bắt đầu tất cả các chuỗi khác, sẽ thực hiện lệnh CountDownLatch.await()gọi chặn. Nó sẽ được phát hành ngay sau khi bộ đếm nội bộ về 0.

Chú ý rằng với vật thể này, một vật InterruptedExceptionthể cũng có thể được ném.


8

Bạn làm

for (Thread t : new Thread[] { th1, th2, th3, th4, th5 })
    t.join()

Sau vòng lặp for này, bạn có thể chắc chắn rằng tất cả các luồng đã hoàn thành công việc của chúng.


6

Chờ / chặn Luồng chính cho đến khi một số luồng khác hoàn thành công việc của chúng.

Như đã @Ravindra babunói, nó có thể đạt được bằng nhiều cách khác nhau, nhưng hãy thể hiện bằng các ví dụ.

  • java.lang.Thread. tham gia () Kể từ: 1.0

    public static void joiningThreads() throws InterruptedException {
        Thread t1 = new Thread( new LatchTask(1, null), "T1" );
        Thread t2 = new Thread( new LatchTask(7, null), "T2" );
        Thread t3 = new Thread( new LatchTask(5, null), "T3" );
        Thread t4 = new Thread( new LatchTask(2, null), "T4" );
    
        // Start all the threads
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    
        // Wait till all threads completes
        t1.join();
        t2.join();
        t3.join();
        t4.join();
    }
  • java.util.concurrent.CountDownLatch Kể từ: 1.5

    • .countDown() «Giảm số lượng của nhóm chốt.
    • .await() «Các phương thức chờ đợi sẽ chặn cho đến khi số lượng hiện tại bằng không.

    Nếu bạn đã tạo latchGroupCount = 4thì countDown()sẽ được gọi 4 lần để đếm 0. Vì vậy, điều đó await()sẽ giải phóng các luồng chặn.

    public static void latchThreads() throws InterruptedException {
        int latchGroupCount = 4;
        CountDownLatch latch = new CountDownLatch(latchGroupCount);
        Thread t1 = new Thread( new LatchTask(1, latch), "T1" );
        Thread t2 = new Thread( new LatchTask(7, latch), "T2" );
        Thread t3 = new Thread( new LatchTask(5, latch), "T3" );
        Thread t4 = new Thread( new LatchTask(2, latch), "T4" );
    
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    
        //latch.countDown();
    
        latch.await(); // block until latchGroupCount is 0.
    }

Mã ví dụ của lớp Threaded LatchTask. Để kiểm tra việc sử dụng phương pháp tiếp cận joiningThreads();latchThreads();phương pháp từ chính.

class LatchTask extends Thread {
    CountDownLatch latch;
    int iterations = 10;
    public LatchTask(int iterations, CountDownLatch latch) {
        this.iterations = iterations;
        this.latch = latch;
    }

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " : Started Task...");

        for (int i = 0; i < iterations; i++) {
            System.out.println(threadName + " : " + i);
            MainThread_Wait_TillWorkerThreadsComplete.sleep(1);
        }
        System.out.println(threadName + " : Completed Task");
        // countDown() « Decrements the count of the latch group.
        if(latch != null)
            latch.countDown();
    }
}
  • CyclicBarriers Một hỗ trợ đồng bộ hóa cho phép một tập hợp tất cả các luồng chờ nhau đạt đến một điểm rào cản chung.CyclicBarriers rất hữu ích trong các chương trình liên quan đến một nhóm các luồng có kích thước cố định mà đôi khi phải đợi nhau. Rào cản được gọi là tuần hoàn vì nó có thể được sử dụng lại sau khi các luồng chờ được giải phóng.
    CyclicBarrier barrier = new CyclicBarrier(3);
    barrier.await();
    Ví dụ, hãy tham khảo lớp Concurrent_ParallelNotifyies này .

  • Executer framework: chúng ta có thể sử dụng ExecutorService để tạo nhóm luồng và theo dõi tiến trình của các tác vụ không đồng bộ với Future.

    • submit(Runnable), submit(Callable)trả về Đối tượng Tương lai. Bằng cách sử dụng future.get()hàm, chúng ta có thể chặn luồng chính cho đến khi các luồng làm việc hoàn thành công việc của nó.

    • invokeAll(...) - trả về danh sách các đối tượng Tương lai mà qua đó bạn có thể nhận được kết quả của các lần thực thi của mỗi Callable.

Tìm ví dụ về việc sử dụng Interfaces Runnable, Callable với khung Executor.


@Xem thêm


4

Lưu trữ các đối tượng Thread vào một số tập hợp (như một Danh sách hoặc một Tập hợp), sau đó lặp qua tập hợp khi các chủ đề được bắt đầu và gọi tham gia () trên các Chủ đề.



2

Mặc dù không liên quan đến vấn đề của OP, nhưng nếu bạn quan tâm đến việc đồng bộ hóa (chính xác hơn là một điểm hẹn) với chính xác một luồng, bạn có thể sử dụng Bộ trao đổi

Trong trường hợp của tôi, tôi cần tạm dừng luồng cha cho đến khi luồng con thực hiện một việc gì đó, chẳng hạn như hoàn thành việc khởi tạo nó. Một CountDownLatch cũng hoạt động tốt.



1

hãy thử điều này, sẽ hiệu quả.

  Thread[] threads = new Thread[10];

  List<Thread> allThreads = new ArrayList<Thread>();

  for(Thread thread : threads){

        if(null != thread){

              if(thread.isAlive()){

                    allThreads.add(thread);

              }

        }

  }

  while(!allThreads.isEmpty()){

        Iterator<Thread> ite = allThreads.iterator();

        while(ite.hasNext()){

              Thread thread = ite.next();

              if(!thread.isAlive()){

                   ite.remove();
              }

        }

   }

1

Tôi đã gặp sự cố tương tự và cuối cùng đã sử dụng Java 8llelStream.

requestList.parallelStream().forEach(req -> makeRequest(req));

Nó siêu đơn giản và dễ đọc. Ở hậu trường, nó đang sử dụng nhóm tham gia fork của JVM mặc định, có nghĩa là nó sẽ đợi tất cả các luồng kết thúc trước khi tiếp tục. Đối với trường hợp của tôi, đó là một giải pháp gọn gàng, bởi vì nó là dòng song song duy nhất trong ứng dụng của tôi. Nếu bạn có nhiều dòng song song chạy đồng thời, vui lòng đọc liên kết bên dưới.

Thông tin thêm về các luồng song song tại đây .


0

Các câu trả lời hiện có cho biết join()mỗi chủ đề có thể .

Nhưng có một số cách để lấy mảng / danh sách luồng:

  • Thêm Chủ đề vào một danh sách khi tạo.
  • Sử dụng ThreadGroupđể quản lý các chủ đề.

Đoạn mã sau sẽ sử dụng ThreadGruopcách tiếp cận. Nó tạo một nhóm trước, sau đó khi tạo mỗi luồng chỉ định nhóm trong hàm tạo, sau đó có thể lấy mảng luồng thông quaThreadGroup.enumerate()


SyncBlockLearn.java

import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * synchronized block - learn,
 *
 * @author eric
 * @date Apr 20, 2015 1:37:11 PM
 */
public class SyncBlockLearn {
    private static final int TD_COUNT = 5; // thread count
    private static final int ROUND_PER_THREAD = 100; // round for each thread,
    private static final long INC_DELAY = 10; // delay of each increase,

    // sync block test,
    @Test
    public void syncBlockTest() throws InterruptedException {
        Counter ct = new Counter();
        ThreadGroup tg = new ThreadGroup("runner");

        for (int i = 0; i < TD_COUNT; i++) {
            new Thread(tg, ct, "t-" + i).start();
        }

        Thread[] tArr = new Thread[TD_COUNT];
        tg.enumerate(tArr); // get threads,

        // wait all runner to finish,
        for (Thread t : tArr) {
            t.join();
        }

        System.out.printf("\nfinal count: %d\n", ct.getCount());
        Assert.assertEquals(ct.getCount(), TD_COUNT * ROUND_PER_THREAD);
    }

    static class Counter implements Runnable {
        private final Object lkOn = new Object(); // the object to lock on,
        private int count = 0;

        @Override
        public void run() {
            System.out.printf("[%s] begin\n", Thread.currentThread().getName());

            for (int i = 0; i < ROUND_PER_THREAD; i++) {
                synchronized (lkOn) {
                    System.out.printf("[%s] [%d] inc to: %d\n", Thread.currentThread().getName(), i, ++count);
                }
                try {
                    Thread.sleep(INC_DELAY); // wait a while,
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.printf("[%s] end\n", Thread.currentThread().getName());
        }

        public int getCount() {
            return count;
        }
    }
}

Luồng chính sẽ đợi tất cả các luồng trong nhóm kết thúc.


0

Tôi đã tạo một phương thức trợ giúp nhỏ để đợi một vài Chủ đề kết thúc:

public static void waitForThreadsToFinish(Thread... threads) {
        try {
            for (Thread thread : threads) {
                thread.join();
            }
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

-1

Sử dụng cái này trong chuỗi chính của bạn: while (! Execute.isTermina ()); Đặt dòng mã này sau khi bắt đầu tất cả các chuỗi từ dịch vụ thực thi. Điều này sẽ chỉ bắt đầu luồng chính sau khi tất cả các luồng do người thực thi bắt đầu kết thúc. Hãy chắc chắn rằng đã gọi execute.shutdown (); trước vòng lặp trên.


Đây là hoạt động chờ đợi, điều này sẽ khiến CPU liên tục chạy một vòng lặp trống. Rất lãng phí.
Adam Michalik
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.