여러 스레드가 완료 될 때까지 기다리는 방법은 무엇입니까?


109

모든 스레드 프로세스가 완료 될 때까지 기다리는 방법은 무엇입니까? 예를 들어 다음과 같이 가정 해 보겠습니다.

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
    }


}

main()모든 스레드의 run()메서드가 종료 될 때까지 메서드가 주석에서 일시 중지 되도록 어떻게 변경 합니까? 감사!

답변:


163

모든 스레드를 배열에 넣고 모두 시작한 다음 루프가 있습니다.

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

각 조인은 해당 스레드가 완료 될 때까지 차단됩니다. 스레드는 결합하는 순서와 다른 순서로 완료 될 수 있지만 문제는 아닙니다. 루프가 종료되면 모든 스레드가 완료됩니다.


1
@Mykola : 스레드 그룹 사용의 장점은 정확히 무엇 입니까? API가 있다고해서 반드시 사용해야하는 것은 아닙니다 ...
Martin v. Löwis

2
참조 : "스레드 그룹은 스레드 집합을 나타냅니다." 이것은이 유스 케이스에 대한 올바른 의미입니다! 그리고 : "스레드는 자신의 스레드 그룹에 대한 정보에 액세스 할 수 있습니다."
Martin K.

4
"Effective Java"라는 책은 스레드 그룹을 피하도록 권장합니다 (항목 73).
Bastien Léonard

2
Effective Java에 언급 된 버그는 Java 6에서 수정되어야합니다. 최신 Java 버전이 제한 사항이 아니라면 Futures를 사용하여 스레드 문제를 해결하는 것이 좋습니다. Martin v. Löwis : 맞습니다. 그 문제와 관련이 없지만 ExecutorService와 같은 하나의 객체에서 실행중인 스레드에 대한 더 많은 정보를 얻는 것이 좋습니다. 주어진 기능을 사용하여 문제를 해결하는 것이 좋다고 생각합니다. 앞으로 더 많은 유연성 (스레드 정보)이 필요할 수 있습니다. 이전 JDK의 이전 버그 클래스를 언급하는 것도 옳습니다.
Martin K.

5
ThreadGroup은 그룹 수준의 조인을 구현하지 않으므로 사람들이 ThreadGroup을 추진하는 이유는 다소 당황 스럽습니다. 사람들이 실제로 스핀 잠금을 사용하고 그룹의 activeCount를 쿼리하고 있습니까? 모든 스레드에서 조인을 호출하는 것과 비교할 때 어떤 식 으로든 그렇게하는 것이 더 낫다는 것을 저를 설득하기 어려울 것입니다.

41

한 가지 방법은 수 있도록하는 것입니다 ListThread목록에 추가하는 동안, S 만들고 각 스레드를 실행합니다. 모든 것이 시작되면 목록을 반복 join()하고 각각을 호출하십시오 . 스레드가 실행을 완료하는 순서는 중요하지 않습니다. 두 번째 루프가 실행을 완료 할 때까지 모든 스레드가 완료된다는 것을 알아야합니다.

더 나은 접근 방식은 ExecutorService 및 관련 메서드 를 사용하는 것입니다 .

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

ExecutorService (및 Java 5 동시성 유틸리티 의 모든 새로운 기능 )를 사용하는 것은 매우 유연하며 위의 예는 표면을 거의 긁지 않습니다.


ThreadGroup은 갈 길입니다! 변경 가능한 목록을 사용하면 문제가 발생합니다 (동기화)
Martin K.

3
뭐? 어떻게 곤경에 처할까요? 시작을 수행하는 스레드에 의해서만 변경 가능합니다 (읽기만 가능). 목록 반복 하는 동안 목록 수정하지 않는 한 괜찮습니다.
Adam Batkin

사용 방법에 따라 다릅니다. 스레드에서 호출 클래스를 사용하면 문제가 발생합니다.
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
   }
}

매력처럼 작동했습니다 ... 여러 쿠키에 대한 문제로 인해 동시에 실행해서는 안되는 두 세트의 스레드가 있습니다. 나는 한 번에 스레드의 한 세트를 실행하기 위해 예를 사용 .. 덕분에 지식 ... 공유
ARN-ARN을

@Dantalian-Runnable 클래스 (예 : run 메서드)에서 발생한 예외를 캡처하여 로컬에 저장하거나 오류 메시지 / 조건을 저장하려고합니다. 예제에서 f.get ()은 ExecutorService에 제출 한 객체를 반환합니다. 객체에는 예외 / 오류 조건을 검색하는 방법이있을 수 있습니다. 제공된 예제를 수정하는 방법에 따라 f.get ()에 의해 변환 된 객체를 예상 유형으로 캐스팅해야 할 수 있습니다.
jt.

12

join()이전 API 인 대신 사용할 수 있습니다. CountDownLatch . 귀하의 요구 사항을 충족시키기 위해 귀하의 코드를 아래와 같이 수정했습니다.

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

설명 :

  1. CountDownLatch 귀하의 요구 사항에 따라 주어진 카운트 1000으로 초기화되었습니다.

  2. 각 작업자 스레드 DoSomethingInAThreadCountDownLatch생성자에 전달 된를 감소시킵니다 .

  3. CountDownLatchDemo await()카운트가 0이 될 때까지 메인 스레드 . 카운트가 0이되면 출력에서 ​​줄 아래에있게됩니다.

    In Main thread after completion of 1000 threads

오라클 문서 페이지에서 더 많은 정보

public void await()
           throws InterruptedException

스레드가 인터럽트되지 않는 한 현재 스레드가 래치가 0이 될 때까지 대기하도록합니다.

다른 옵션은 관련 SE 질문을 참조하십시오.

모든 스레드가 Java에서 작업을 마칠 때까지 기다립니다.


8

Thread 클래스를 아예 피하고 대신 java.util.concurrent에서 제공하는 더 높은 추상화를 사용하십시오.

ExecutorService 클래스는 원하는 작업을 수행하는 것 같은 invokeAll 메소드를 제공합니다 .


6

사용하는 것이 좋습니다 java.util.concurrent.CountDownLatch. javadocs의


스레드에 대한 래치이며 래치 잠금은 카운트 다운과 함께 작동합니다. 스레드의 run () 메서드에서 CountDownLatch가 0으로 카운트 다운 될 때까지 기다리도록 명시 적으로 선언합니다. 둘 이상의 스레드에서 동일한 CountDownLatch를 사용하여 동시에 해제 할 수 있습니다. 나는 그것이 당신에게 필요한 것인지 모르겠지만, 다중 스레드 환경에서 작업 할 때 유용하기 때문에 그것을 언급하고 싶었습니다.
Pablo Cavalieri

대답 본문에 그 설명을 넣어야할까요?
Aaron Hall

Javadoc의 예제는 매우 설명 적이기 때문에 추가하지 않았습니다. docs.oracle.com/javase/7/docs/api/java/util/concurrent/... . 첫 번째 예제에서 모든 작업자 스레드는 CountdownLatch startSignal이 0에 도달 할 때까지 기다리기 때문에 동시에 릴리스됩니다. 이는 startSignal.countDown ()에서 발생합니다. 그런 다음 mian 스레드는 doneSignal.await () 명령을 사용하여 모든 작업이 완료 될 때까지 기다립니다. doneSignal은 각 작업자의 값을 줄입니다.
Pablo Cavalieri

6

Martin K가 제안했듯이 이에 java.util.concurrent.CountDownLatch대한 더 나은 해결책 인 것 같습니다. 동일한 예를 추가하기 만하면

     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

필요에 따라 java.util.concurrent 패키지의 CountDownLatch 및 CyclicBarrier 클래스를 확인할 수도 있습니다. 스레드가 서로를 기다리도록하거나 스레드가 실행되는 방식을보다 세밀하게 제어하려는 경우에 유용 할 수 있습니다 (예 : 다른 스레드가 상태를 설정하기 위해 내부 실행에서 대기). 또한 CountDownLatch를 사용하여 루프를 반복 할 때 하나씩 시작하는 대신 모든 스레드가 동시에 시작하도록 신호를 보낼 수 있습니다. 표준 API 문서에는 이에 대한 예제가 있으며 다른 CountDownLatch를 사용하여 모든 스레드가 실행을 완료 할 때까지 대기합니다.



1

첫 번째 for 루프 안에 스레드 개체를 만듭니다.

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

그리고 여기 모든 사람들이 말하는 것입니다.

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

0

Object "ThreadGroup"및 해당 매개 변수 activeCount를 사용하여 수행 할 수 있습니다 .


정확히 어떻게 제안하는지 잘 모르겠습니다. 루프에서 activeCount를 폴링하도록 제안하는 경우 : 바쁘기 때문에 좋지 않습니다.
Martin v. Löwis

@Martin v. Löwis : "Join은 단일 스레드 만 기다립니다. 더 나은 솔루션은 java.util.concurrent.CountDownLatch 일 수 있습니다. 작업자 스레드 수로 설정된 카운트로 래치를 초기화하면됩니다. 각 작업자 스레드는 호출해야합니다. countDown ()이 종료되기 직전에 메인 스레드는 카운터가 0에 도달 할 때까지 차단되는 await ()를 호출합니다. join ()의 문제는 더 많은 스레드를 동적으로 추가 할 수 없다는 것입니다. 목록이 폭발 할 것입니다. 동시 수정. " 귀하의 솔루션은 문제에 대해서는 잘 작동하지만 일반적인 용도로는 작동하지 않습니다.
Martin K.

0

에 대한 대안으로 CountDownLatch를 당신은 또한 사용할 수 있습니다 으로 CyclicBarrier 예를 들어,

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

이 경우 CyclicBarrier를 사용하려면 barrier.await ()가 마지막 문이어야합니다. 즉, 스레드가 작업을 완료했을 때입니다. CyclicBarrier는 reset () 메서드로 다시 사용할 수 있습니다. javadocs를 인용하려면 :

CyclicBarrier는 파티의 마지막 스레드가 도착한 후 스레드가 해제되기 전에 장벽 지점 당 한 번 실행되는 선택적 실행 가능 명령을 지원합니다. 이 장벽 조치는 당사자가 계속하기 전에 공유 상태를 업데이트하는 데 유용합니다.


이것이 CyclicBarrier의 좋은 예라고 생각하지 않습니다. Thread.sleep () 호출을 사용하는 이유는 무엇입니까?
Guenther 2016 년

@Guenther-예, 요구 사항에 맞게 코드를 변경했습니다.
shailendra1118

CyclicBarrier는 CountDownLatch의 대안이 아닙니다. 스레드가 반복적으로 카운트 다운해야하는 경우 CyclicBarrier를 생성해야합니다. 그렇지 않으면 CountDownLatch로 기본 설정됩니다 (그렇지 않으면 실행의 추가 추상화가 필요한 경우가 아니면 더 높은 수준의 서비스를 찾아야 함).
Elysiumplain

0

join()나에게 도움이되지 않았습니다. 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()
        }
    })

결과:

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

이제 join()for 스레드를 사용하겠습니다 .

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

그 결과 :

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

다음을 사용할 때 분명합니다 join.

  1. 스레드가 순차적으로 실행됩니다.
  2. 첫 번째 샘플은 765 밀리 초가 걸리고 두 번째 샘플은 3833 밀리 초가 걸립니다.

다른 스레드를 차단하는 것을 방지하기위한 솔루션은 ArrayList를 만드는 것입니다.

val threads = ArrayList<Thread>()

이제 새 스레드를 시작하려면 ArrayList에 추가합니다.

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

addThreadToArray기능 :

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

startNewThreadfunstion :

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

필요한 모든 곳에서 아래의 스레드 완료를 확인하십시오.

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.
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.