Làm thế nào để đợi một số chủ đề hoàn thành?


109

Cách đơn giản là đợi tất cả quá trình phân luồng kết thúc là gì? Ví dụ: giả sử tôi có:

public class DoSomethingInAThread implements Runnable{

    public static void main(String[] args) {
        for (int n=0; n<1000; n++) {
            Thread t = new Thread(new DoSomethingInAThread());
            t.start();
        }
        // wait for all threads' run() methods to complete before continuing
    }

    public void run() {
        // do something here
    }


}

Làm cách nào để thay đổi điều này để main()phương thức tạm dừng ở nhận xét cho đến khi tất cả các run()phương thức của chủ đề thoát ra? Cảm ơn!

Câu trả lời:


163

Bạn đặt tất cả các chuỗi trong một mảng, bắt đầu tất cả và sau đó có một vòng lặp

for(i = 0; i < threads.length; i++)
  threads[i].join();

Mỗi tham gia sẽ chặn cho đến khi chuỗi tương ứng đã hoàn thành. Các chuỗi có thể hoàn thành theo một thứ tự khác với việc bạn tham gia chúng, nhưng đó không phải là vấn đề: khi vòng lặp thoát ra, tất cả các chuỗi đã hoàn thành.


1
@Mykola: chính xác thì lợi thế của việc sử dụng một nhóm chủ đề là gì? Chỉ vì API ở đó không có nghĩa là bạn phải sử dụng nó ...
Martin v. Löwis

2
Xem: "Một nhóm chủ đề đại diện cho một tập hợp các chủ đề." Đây là ngữ nghĩa chính xác cho trường hợp sử dụng này! Và: "Một chủ đề được phép truy cập thông tin về nhóm chủ đề của chính nó"
Martin K.

4
Cuốn sách “Java hiệu quả” khuyên bạn nên tránh các nhóm luồng (mục 73).
Bastien Léonard 09/08/09

2
Các lỗi được đề cập trong Java hiệu quả đáng lẽ đã được sửa trong Java 6. Nếu các phiên bản java mới hơn không phải là một hạn chế, tốt hơn nên sử dụng Futures để giải quyết các vấn đề về luồng. Martin kiện Löwis: Bạn nói đúng. Nó không liên quan đến vấn đề đó, nhưng thật tuyệt khi có thêm Thông tin về các luồng đang chạy từ một Đối tượng (như ExecutorService). Tôi nghĩ thật tuyệt khi sử dụng các tính năng nhất định để giải quyết một vấn đề; có thể bạn sẽ cần sự linh hoạt hơn (thông tin luồng) trong tương lai. Cũng đúng khi đề cập đến các lớp lỗi cũ trong các JDK cũ hơn.
Martin K.

5
ThreadGroup không triển khai tham gia cấp nhóm, vì vậy tại sao mọi người lại thúc đẩy ThreadGroup là một chút khó hiểu. Mọi người có thực sự đang sử dụng khóa quay và truy vấn Tài khoản hoạt động của nhóm không? Bạn sẽ khó thuyết phục tôi rằng làm như vậy sẽ tốt hơn theo bất kỳ cách nào so với việc chỉ gọi tham gia trên tất cả các chuỗi.

41

Một cách sẽ là tạo một Listtrong các Threads, tạo và khởi chạy từng luồng, đồng thời thêm nó vào danh sách. Sau khi mọi thứ được khởi chạy, hãy lặp lại danh sách và gọi join()từng cái. Không quan trọng thứ tự mà các luồng kết thúc thực thi theo thứ tự nào, tất cả những gì bạn cần biết là vào thời điểm vòng lặp thứ hai kết thúc thực thi, mọi luồng sẽ hoàn thành.

Một cách tiếp cận tốt hơn là sử dụng ExecutorService và các phương thức liên quan của nó:

List<Callable> callables = ... // assemble list of Callables here
                               // Like Runnable but can return a value
ExecutorService execSvc = Executors.newCachedThreadPool();
List<Future<?>> results = execSvc.invokeAll(callables);
// Note: You may not care about the return values, in which case don't
//       bother saving them

Sử dụng ExecutorService (và tất cả những thứ mới từ các tiện ích đồng thời của Java 5 ) cực kỳ linh hoạt và ví dụ trên hầu như không làm trầy xước bề mặt.


ThreadGroup là con đường để đi! Với Danh sách có thể thay đổi, bạn sẽ gặp rắc rối (đồng bộ hóa)
Martin K.

3
Gì? Bạn sẽ gặp rắc rối như thế nào? Nó chỉ có thể thay đổi (chỉ có thể đọc được) bởi luồng đang thực hiện khởi chạy, vì vậy, miễn là nó không sửa đổi danh sách trong khi lặp qua nó, thì tốt.
Adam Batkin

Nó phụ thuộc vào cách bạn sử dụng nó. Nếu bạn sử dụng lớp gọi trong một chuỗi, bạn sẽ gặp sự cố.
Martin K.

27
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class DoSomethingInAThread implements Runnable
{
   public static void main(String[] args) throws ExecutionException, InterruptedException
   {
      //limit the number of actual threads
      int poolSize = 10;
      ExecutorService service = Executors.newFixedThreadPool(poolSize);
      List<Future<Runnable>> futures = new ArrayList<Future<Runnable>>();

      for (int n = 0; n < 1000; n++)
      {
         Future f = service.submit(new DoSomethingInAThread());
         futures.add(f);
      }

      // wait for all tasks to complete before continuing
      for (Future<Runnable> f : futures)
      {
         f.get();
      }

      //shut down the executor service so that this thread can exit
      service.shutdownNow();
   }

   public void run()
   {
      // do something here
   }
}

hoạt động như một sự quyến rũ ... tôi có hai bộ luồng không nên chạy đồng thời vì sự cố trên nhiều cookie. Tôi sử dụng ví dụ của bạn để chạy một bộ đề tại một thời điểm .. nhờ để chia sẻ kiến thức của bạn ...
ARN-ARN

@Dantalian - Trong lớp Runnable của bạn (có thể là trong phương thức chạy), bạn muốn nắm bắt bất kỳ ngoại lệ nào đã xảy ra và lưu trữ chúng cục bộ (hoặc lưu trữ một thông báo / điều kiện lỗi). Trong ví dụ, f.get () trả về đối tượng của bạn mà bạn đã gửi cho ExecutorService. Đối tượng của bạn có thể có một phương thức để truy xuất bất kỳ trường hợp ngoại lệ / lỗi nào. Tùy thuộc vào cách bạn sửa đổi ví dụ đã cung cấp, bạn có thể cần truyền đối tượng do f.get () chuyển sang loại mong đợi của bạn.
jt.

12

thay vì join()là một API cũ, bạn có thể sử dụng CountDownLatch . Tôi đã sửa đổi mã của bạn như bên dưới để đáp ứng yêu cầu của bạn.

import java.util.concurrent.*;
class DoSomethingInAThread implements Runnable{
    CountDownLatch latch;
    public DoSomethingInAThread(CountDownLatch latch){
        this.latch = latch;
    } 
    public void run() {
        try{
            System.out.println("Do some thing");
            latch.countDown();
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

public class CountDownLatchDemo {
    public static void main(String[] args) {
        try{
            CountDownLatch latch = new CountDownLatch(1000);
            for (int n=0; n<1000; n++) {
                Thread t = new Thread(new DoSomethingInAThread(latch));
                t.start();
            }
            latch.await();
            System.out.println("In Main thread after completion of 1000 threads");
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

Giải thích :

  1. CountDownLatch đã được khởi tạo với số lượng cho trước là 1000 theo yêu cầu của bạn.

  2. Mỗi luồng công nhân DoSomethingInAThread sẽ giảm CountDownLatch, đã được truyền trong hàm khởi tạo.

  3. Chủ đề chính CountDownLatchDemo await()cho đến khi số đếm trở thành số không. Khi số đếm đã trở thành 0, bạn sẽ nhận được dòng dưới đầu ra.

    In Main thread after completion of 1000 threads

Thông tin thêm từ trang tài liệu oracle

public void await()
           throws InterruptedException

Làm cho luồng hiện tại đợi cho đến khi chốt đếm ngược về 0, trừ khi luồng bị ngắt.

Tham khảo câu hỏi SE liên quan để biết các tùy chọn khác:

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


8

Tránh hoàn toàn lớp Thread và thay vào đó sử dụng các phần trừu tượng cao hơn được cung cấp trong java.util.concurrent

Lớp ExecutorService cung cấp phương thức invokeAll dường như chỉ làm những gì bạn muốn.


6

Cân nhắc sử dụng java.util.concurrent.CountDownLatch. Ví dụ trong javadocs


Là một chốt cho các luồng, khóa chốt hoạt động với một bộ đếm ngược. Trong phương thức run () của luồng của bạn, hãy khai báo một cách rõ ràng để đợi một CountDownLatch đạt đến nó sẽ đếm ngược về 0. Bạn có thể sử dụng cùng một CountDownLatch trong nhiều luồng để giải phóng chúng đồng thời. Tôi không biết đó có phải là thứ bạn cần không, chỉ muốn đề cập đến nó vì nó hữu ích khi làm việc trong môi trường đa luồng.
Pablo Cavalieri

Có lẽ bạn nên đưa lời giải thích đó vào phần nội dung câu trả lời của mình?
Aaron Hall

Các ví dụ trong Javadoc rất mô tả, đó là lý do tại sao tôi không thêm bất kỳ ví dụ nào. docs.oracle.com/javase/7/docs/api/java/util/concurrent/… . Trong ví dụ đầu tiên, tất cả các luồng Công nhân được phát hành đồng thời bởi vì chúng đợi startSignal CountdownLatch về 0, điều này xảy ra trong startSignal.countDown (). Sau đó, luồng mian đợi cho đến khi tất cả các công việc kết thúc bằng cách sử dụng lệnh doneSignal.await (). doneSignal giảm giá trị của nó trong mỗi công nhân.
Pablo Cavalieri

6

Như Martin K đề xuất java.util.concurrent.CountDownLatchcó vẻ là một giải pháp tốt hơn cho điều này. Chỉ cần thêm một ví dụ cho cùng một

     public class CountDownLatchDemo
{

    public static void main (String[] args)
    {
        int noOfThreads = 5;
        // Declare the count down latch based on the number of threads you need
        // to wait on
        final CountDownLatch executionCompleted = new CountDownLatch(noOfThreads);
        for (int i = 0; i < noOfThreads; i++)
        {
            new Thread()
            {

                @Override
                public void run ()
                {

                    System.out.println("I am executed by :" + Thread.currentThread().getName());
                    try
                    {
                        // Dummy sleep
                        Thread.sleep(3000);
                        // One thread has completed its job
                        executionCompleted.countDown();
                    }
                    catch (InterruptedException e)
                    {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

            }.start();
        }

        try
        {
            // Wait till the count down latch opens.In the given case till five
            // times countDown method is invoked
            executionCompleted.await();
            System.out.println("All over");
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

}

4

Tùy thuộc vào nhu cầu của bạn, bạn cũng có thể muốn kiểm tra các lớp CountDownLatch và CyclicBarrier trong gói java.util.concurrent. Chúng có thể hữu ích nếu bạn muốn các luồng của mình đợi lẫn nhau hoặc nếu bạn muốn kiểm soát chi tiết hơn cách các luồng của bạn thực thi (ví dụ: chờ trong quá trình thực thi nội bộ của chúng để một luồng khác thiết lập một số trạng thái). Bạn cũng có thể sử dụng CountDownLatch để báo hiệu tất cả các chuỗi của bạn bắt đầu cùng một lúc, thay vì bắt đầu từng chuỗi một khi bạn lặp qua vòng lặp của mình. Các tài liệu API tiêu chuẩn có một ví dụ về điều này, cùng với việc sử dụng CountDownLatch khác để đợi tất cả các luồng hoàn tất quá trình thực thi của chúng.


3

Nếu bạn tạo danh sách các chuỗi, bạn có thể lặp qua chúng và .join () đối với từng chuỗi, và vòng lặp của bạn sẽ kết thúc khi tất cả các chuỗi đều có. Tôi đã không thử nó mặc dù.

http://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#join ()


Xin chào, nó không hoạt động với tôi vì một số lý do. Đây là câu hỏi của tôi: stackoverflow.com/users/5144855/ruchir-baronia
Ruchir Baronia

1

Tạo đối tượng luồng bên trong vòng lặp for đầu tiên.

for (int i = 0; i < threads.length; i++) {
     threads[i] = new Thread(new Runnable() {
         public void run() {
             // some code to run in parallel
         }
     });
     threads[i].start();
 }

Và sau đó những gì mọi người ở đây đang nói.

for(i = 0; i < threads.length; i++)
  threads[i].join();

0

Bạn có thể làm điều đó với Đối tượng "ThreadGroup" và tham số activeCount của nó :


Không chắc chắn chính xác cách bạn đề xuất làm điều đó. Nếu bạn đề xuất thăm dò Số lượng hoạt động trong một vòng lặp: điều đó thật tệ, vì nó đang bận - hãy đợi (ngay cả khi bạn ngủ giữa các cuộc thăm dò - khi đó bạn sẽ phải cân bằng giữa kinh doanh và khả năng phản hồi).
Martin kiện Löwis

@Martin v. Löwis: "Tham gia sẽ chỉ đợi một luồng duy nhất. Giải pháp tốt hơn có thể là java.util.concurrent.CountDownLatch. Chỉ cần khởi tạo chốt với bộ đếm được đặt thành số luồng công nhân. Mỗi luồng công nhân sẽ gọi countDown () ngay trước khi nó thoát và luồng chính chỉ đơn giản gọi là await (), sẽ chặn cho đến khi bộ đếm về 0. Vấn đề với join () cũng là bạn không thể bắt đầu thêm động các luồng khác. Danh sách sẽ phát nổ với một sửa đổi đồng thời. " Giải pháp của bạn phù hợp với Sự cố nhưng không phù hợp với mục đích chung.
Martin K.

0

Để thay thế cho CountDownLatch, bạn cũng có thể sử dụng CyclicBarrier, ví dụ:

public class ThreadWaitEx {
    static CyclicBarrier barrier = new CyclicBarrier(100, new Runnable(){
        public void run(){
            System.out.println("clean up job after all tasks are done.");
        }
    });
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            Thread t = new Thread(new MyCallable(barrier));
            t.start();
        }       
    }

}    

class MyCallable implements Runnable{
    private CyclicBarrier b = null;
    public MyCallable(CyclicBarrier b){
        this.b = b;
    }
    @Override
    public void run(){
        try {
            //do something
            System.out.println(Thread.currentThread().getName()+" is waiting for barrier after completing his job.");
            b.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }       
}

Để sử dụng CyclicBarrier trong trường hợp này, rào cản.await () phải là câu lệnh cuối cùng tức là khi luồng của bạn hoàn thành công việc của nó. CyclicBarrier có thể được sử dụng lại với phương thức reset () của nó. Để trích dẫn javadocs:

Một CyclicBarrier hỗ trợ một lệnh Runnable tùy chọn được chạy một lần cho mỗi điểm rào cản, sau khi luồng cuối cùng trong nhóm đến, nhưng trước khi bất kỳ luồng nào được phát hành. Hành động rào cản này hữu ích để cập nhật trạng thái chia sẻ trước khi bất kỳ bên nào tiếp tục.


Tôi không nghĩ đó là một ví dụ điển hình cho CyclicBarrier. Tại sao bạn sử dụng lệnh gọi Thread.sleep ()?
Guenther

@Guenther - vâng, tôi đã thay đổi mã để phù hợp với yêu cầu.
shailendra1118

CyclicBarrier không phải là một giải pháp thay thế cho CountDownLatch. Khi các luồng liên tục phải đếm ngược, bạn nên tạo một CyclicBarrier, nếu không thì mặc định là CountDownLatch (trừ khi yêu cầu thêm phần trừu tượng của Thực thi, lúc này bạn nên tìm đến cấp cao hơn, Dịch vụ).
Elysiumplain

0

Các join()không hữu ích cho tôi. xem mẫu này trong Kotlin:

    val timeInMillis = System.currentTimeMillis()
    ThreadUtils.startNewThread(Runnable {
        for (i in 1..5) {
            val t = Thread(Runnable {
                Thread.sleep(50)
                var a = i
                kotlin.io.println(Thread.currentThread().name + "|" + "a=$a")
                Thread.sleep(200)
                for (j in 1..5) {
                    a *= j
                    Thread.sleep(100)
                    kotlin.io.println(Thread.currentThread().name + "|" + "$a*$j=$a")
                }
                kotlin.io.println(Thread.currentThread().name + "|TaskDurationInMillis = " + (System.currentTimeMillis() - timeInMillis))
            })
            t.start()
        }
    })

Kết quả:

Thread-5|a=5
Thread-1|a=1
Thread-3|a=3
Thread-2|a=2
Thread-4|a=4
Thread-2|2*1=2
Thread-3|3*1=3
Thread-1|1*1=1
Thread-5|5*1=5
Thread-4|4*1=4
Thread-1|2*2=2
Thread-5|10*2=10
Thread-3|6*2=6
Thread-4|8*2=8
Thread-2|4*2=4
Thread-3|18*3=18
Thread-1|6*3=6
Thread-5|30*3=30
Thread-2|12*3=12
Thread-4|24*3=24
Thread-4|96*4=96
Thread-2|48*4=48
Thread-5|120*4=120
Thread-1|24*4=24
Thread-3|72*4=72
Thread-5|600*5=600
Thread-4|480*5=480
Thread-3|360*5=360
Thread-1|120*5=120
Thread-2|240*5=240
Thread-1|TaskDurationInMillis = 765
Thread-3|TaskDurationInMillis = 765
Thread-4|TaskDurationInMillis = 765
Thread-5|TaskDurationInMillis = 765
Thread-2|TaskDurationInMillis = 765

Bây giờ hãy để tôi sử dụng join()cho các chủ đề:

    val timeInMillis = System.currentTimeMillis()
    ThreadUtils.startNewThread(Runnable {
        for (i in 1..5) {
            val t = Thread(Runnable {
                Thread.sleep(50)
                var a = i
                kotlin.io.println(Thread.currentThread().name + "|" + "a=$a")
                Thread.sleep(200)
                for (j in 1..5) {
                    a *= j
                    Thread.sleep(100)
                    kotlin.io.println(Thread.currentThread().name + "|" + "$a*$j=$a")
                }
                kotlin.io.println(Thread.currentThread().name + "|TaskDurationInMillis = " + (System.currentTimeMillis() - timeInMillis))
            })
            t.start()
            t.join()
        }
    })

Và kết quả:

Thread-1|a=1
Thread-1|1*1=1
Thread-1|2*2=2
Thread-1|6*3=6
Thread-1|24*4=24
Thread-1|120*5=120
Thread-1|TaskDurationInMillis = 815
Thread-2|a=2
Thread-2|2*1=2
Thread-2|4*2=4
Thread-2|12*3=12
Thread-2|48*4=48
Thread-2|240*5=240
Thread-2|TaskDurationInMillis = 1568
Thread-3|a=3
Thread-3|3*1=3
Thread-3|6*2=6
Thread-3|18*3=18
Thread-3|72*4=72
Thread-3|360*5=360
Thread-3|TaskDurationInMillis = 2323
Thread-4|a=4
Thread-4|4*1=4
Thread-4|8*2=8
Thread-4|24*3=24
Thread-4|96*4=96
Thread-4|480*5=480
Thread-4|TaskDurationInMillis = 3078
Thread-5|a=5
Thread-5|5*1=5
Thread-5|10*2=10
Thread-5|30*3=30
Thread-5|120*4=120
Thread-5|600*5=600
Thread-5|TaskDurationInMillis = 3833

Như đã rõ khi chúng ta sử dụng join:

  1. Các chủ đề đang chạy tuần tự.
  2. Mẫu đầu tiên mất 765 mili giây trong khi mẫu thứ hai mất 3833 mili giây.

Giải pháp của chúng tôi để ngăn chặn các luồng khác là tạo ArrayList:

val threads = ArrayList<Thread>()

Bây giờ khi chúng ta muốn bắt đầu một luồng mới, chúng ta nhất là thêm nó vào ArrayList:

addThreadToArray(
    ThreadUtils.startNewThread(Runnable {
        ...
    })
)

Các addThreadToArray chức năng:

@Synchronized
fun addThreadToArray(th: Thread) {
    threads.add(th)
}

Các startNewThread funstion:

fun startNewThread(runnable: Runnable) : Thread {
    val th = Thread(runnable)
    th.isDaemon = false
    th.priority = Thread.MAX_PRIORITY
    th.start()
    return th
}

Kiểm tra việc hoàn thành các chuỗi như bên dưới ở mọi nơi cần thiết:

val notAliveThreads = ArrayList<Thread>()
for (t in threads)
    if (!t.isAlive)
        notAliveThreads.add(t)
threads.removeAll(notAliveThreads)
if (threads.size == 0){
    // The size is 0 -> there is no alive threads.
}
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.