다른 스레드가 완료되었는지 확인하는 방법은 무엇입니까?


126

StartDownload()세 개의 스레드를 시작 하는라는 메서드가있는 객체가 있습니다.

각 스레드의 실행이 완료되면 알림을 받으려면 어떻게합니까?

스레드 중 하나 (또는 ​​모두)가 완료되었거나 여전히 실행 중인지 알 수있는 방법이 있습니까?


답변:


228

이를 수행 할 수있는 여러 가지 방법이 있습니다.

  1. 사용 를 Thread.join () 주 스레드에서 각 스레드가 완료 될 때까지 차단 방식으로 기다리거나하는
  2. 각 스레드가 완료 될 때까지 기다리려면 폴링 방식으로 Thread.isAlive () 를 확인하십시오 ( 일반적으로 권장하지 않음).
  3. Unorthodox는 문제의 각 스레드에 대해 setUncaughtExceptionHandler 를 호출하여 객체의 메소드를 호출하고 각 스레드가 완료 될 때 포착되지 않은 예외를 처리하도록 프로그래밍합니다.
  4. 에서 잠금 또는 싱크로 나이저 또는 메커니즘을 사용 하고있는 java.util.concurrent , 또는
  5. 더 많은 정통, 기본 스레드에서 리스너를 만든 다음 각 스레드를 프로그래밍하여 리스너에게 완료되었음을 알리십시오.

아이디어 # 5를 구현하는 방법? 한 가지 방법은 먼저 인터페이스를 만드는 것입니다.

public interface ThreadCompleteListener {
    void notifyOfThreadComplete(final Thread thread);
}

그런 다음 다음 클래스를 만듭니다.

public abstract class NotifyingThread extends Thread {
  private final Set<ThreadCompleteListener> listeners
                   = new CopyOnWriteArraySet<ThreadCompleteListener>();
  public final void addListener(final ThreadCompleteListener listener) {
    listeners.add(listener);
  }
  public final void removeListener(final ThreadCompleteListener listener) {
    listeners.remove(listener);
  }
  private final void notifyListeners() {
    for (ThreadCompleteListener listener : listeners) {
      listener.notifyOfThreadComplete(this);
    }
  }
  @Override
  public final void run() {
    try {
      doRun();
    } finally {
      notifyListeners();
    }
  }
  public abstract void doRun();
}

그런 다음 각 스레드가 확장 NotifyingThread되고 구현 run()하는 대신 구현 doRun()됩니다. 따라서 완료되면 알림을 기다리는 사람에게 자동으로 알립니다.

마지막으로, 모든 스레드를 시작하는 클래스 (또는 적어도 알림을 기다리는 객체)의 기본 클래스에서 implement ThreadCompleteListener각 스레드를 생성 한 직후와 즉시 해당 클래스를 리스너 목록에 추가하십시오.

NotifyingThread thread1 = new OneOfYourThreads();
thread1.addListener(this); // add ourselves as a listener
thread1.start();           // Start the Thread

그런 다음 각 스레드가 종료되면 notifyOfThreadComplete방금 완료되었거나 충돌 한 Thread 인스턴스와 함께 메서드가 호출됩니다.

더 잘 참고하는 것 implements Runnable보다는 extends Thread위한 NotifyingThread스레드를 확장하는 것은 일반적으로 새로운 코드에 낙심 될 때. 그러나 나는 당신의 질문에 코딩하고 있습니다. NotifyingThread구현 하도록 클래스를 변경하면 Runnable스레드를 관리하는 코드 중 일부를 변경해야합니다. 이는 매우 간단합니다.


안녕하세요!! 나는 마지막 생각을 좋아한다. 그렇게하기 위해 리스너를 구현해야합니까? 감사합니다
Ricardo Felgueiras

4
그러나이 접근법을 사용하면 notifiyListeners가 run () 내부에서 호출되므로 스레드를 insisde로 호출하고 추가 호출도 수행됩니다. 그렇지 않습니까?
Jordi Puigdellívol 2016 년

1
@Eddie Jordi는 notify메소드 내부가 아니라 메소드 를 호출 할 수 있는지 묻습니다 run.
Tomasz Dzięcielewski 2016

1
문제는 실제로 보조 스레드 를 어떻게 해제 합니까? 완료된 것을 알고 있지만 지금 메인 스레드에 어떻게 액세스 합니까?
Patrick

1
이 스레드는 안전합니까? notifyListeners (따라서 notifyOfThreadComplete)는 리스너 자체를 작성한 스레드가 아닌 NotifyingThread 내에서 호출됩니다.
Aaron

13

CyclicBarrier를 사용한 솔루션

public class Downloader {
  private CyclicBarrier barrier;
  private final static int NUMBER_OF_DOWNLOADING_THREADS;

  private DownloadingThread extends Thread {
    private final String url;
    public DownloadingThread(String url) {
      super();
      this.url = url;
    }
    @Override
    public void run() {
      barrier.await(); // label1
      download(url);
      barrier.await(); // label2
    }
  }
  public void startDownload() {
    // plus one for the main thread of execution
    barrier = new CyclicBarrier(NUMBER_OF_DOWNLOADING_THREADS + 1); // label0
    for (int i = 0; i < NUMBER_OF_DOWNLOADING_THREADS; i++) {
      new DownloadingThread("http://www.flickr.com/someUser/pic" + i + ".jpg").start();
    }
    barrier.await(); // label3
    displayMessage("Please wait...");
    barrier.await(); // label4
    displayMessage("Finished");
  }
}

label0- 주기적 스레드는 실행 스레드 수에 더한 당사자 수와 기본 실행 스레드 하나 (startDownload ()가 실행되는 스레드 수)로 생성됩니다.

라벨 1 -n 번째 다운로드 스레드가 대기실에 들어갑니다.

라벨 3 -NUMBER_OF_DOWNLOADING_THREADS가 대기실에 입장했습니다. 주요 실행 스레드는 거의 동시에 다운로드 작업을 시작하기 위해 릴리스합니다.

레이블 4- 주요 실행 스레드가 대기실에 들어갑니다. 이것은 이해하기 쉬운 코드 중 가장 까다로운 부분입니다. 대기실에 두 번째 스레드가 들어가는 것은 중요하지 않습니다. 실에 들어가는 스레드가 무엇이든 다른 모든 다운로드 스레드가 다운로드 작업을 완료했는지 확인하는 것이 중요합니다.

라벨 2 -n 번째 다운로드 중 스레드가 다운로드 작업을 마치고 대기실로 들어갑니다. 그것이 메인 실행 스레드를 포함하여 이미 NUMBER_OF_DOWNLOADING_THREADS에 들어간 마지막 스레드 인 경우 메인 스레드는 다른 모든 스레드가 다운로드를 완료 한 경우에만 실행을 계속합니다.


9

당신은해야 정말 사용하는 솔루션을 선호합니다 java.util.concurrent. 주제에서 Josh Bloch 및 / 또는 Brian Goetz를 찾아 읽으십시오.

java.util.concurrent.*쓰레드를 직접 사용 하지 않고 직접 사용할 책임 이 있다면 join(), 쓰레드가 언제 완료되는지 알아야한다. 다음은 매우 간단한 콜백 메커니즘입니다. 먼저 Runnable인터페이스를 확장하여 콜백을 갖습니다.

public interface CallbackRunnable extends Runnable {
    public void callback();
}

그런 다음 런너 블을 실행할 실행자를 만들고 완료되면 다시 전화하십시오.

public class CallbackExecutor implements Executor {

    @Override
    public void execute(final Runnable r) {
        final Thread runner = new Thread(r);
        runner.start();
        if ( r instanceof CallbackRunnable ) {
            // create a thread to perform the callback
            Thread callerbacker = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // block until the running thread is done
                        runner.join();
                        ((CallbackRunnable)r).callback();
                    }
                    catch ( InterruptedException e ) {
                        // someone doesn't want us running. ok, maybe we give up.
                    }
                }
            });
            callerbacker.start();
        }
    }

}

CallbackRunnable인터페이스 에 추가해야 할 또 다른 확실한 것은 예외를 처리하는 수단이므로, public void uncaughtException(Throwable e);거기에 행을 넣고 실행기에 Thread.UncaughtExceptionHandler를 설치하여 해당 인터페이스 메소드로 보내십시오.

그러나 실제로 모든 냄새를 맡기 시작합니다 java.util.concurrent.Callable. java.util.concurrent프로젝트에서 허용하는 경우 실제로 사용을 살펴 봐야 합니다.


runner.join()스레드가 완료되었음을 알고 있기 때문에이 콜백 메커니즘에서 얻는 것, 단순히 호출 한 다음 그 후 원하는 코드 에 대해 명확하지 않습니다 . 해당 코드를 런너 블의 속성으로 정의하는 것만으로 다른 런너 블에 대해 다른 것을 가질 수 있습니까?
Stephen

2
예, runner.join()기다리는 가장 직접적인 방법입니다. OP가 각 다운로드에 대해 "알림"을 요청했기 때문에 OP가 주요 호출 스레드를 차단하고 싶지 않다고 가정했습니다.이 순서는 완료 될 수 있습니다. 이를 통해 비동기식으로 알림을받을 수 있습니다.
broc.seib

4

그들이 끝날 때까지 기다리시겠습니까? 그렇다면 Join 메서드를 사용하십시오.

확인하려는 경우 isAlive 속성도 있습니다.


3
스레드가 아직 실행을 시작하지 않은 경우 (자신의 스레드가 이미 start를 호출 한 경우에도) isAlive는 false를 반환합니다.
Tom Hawtin-tackline

@ TomHawtin-tackline 확실합니까? Java 문서와는 모순됩니다 ( "스레드가 시작되었지만 아직 종료되지 않은 경우 스레드가 있습니다" -docs.oracle.com/javase/6/docs/api/java/lang/… ). 또한 여기에 대한 답변과 모순됩니다 ( stackoverflow.com/questions/17293304/… )
Stephen

@Stephen 내가 쓴지 오래되었지만 사실 인 것 같습니다. 나는 그것이 9 년 전에 다른 사람들에게 문제가 생겨 났다고 생각한다. 정확히 관찰 할 수있는 것은 구현에 달려 있습니다. 스레드가 수행 하는 Threadto start에 알리지 만 호출은 즉시 반환됩니다. isAlive간단한 플래그 테스트 여야하지만 Google을 검색했을 때 방법은 native입니다.
Tom Hawtin-tackline

4

getState ()로 스레드 인스턴스를 조사하여 다음 값 중 하나를 사용하여 Thread.State 열거의 인스턴스를 리턴합니다.

*  NEW
  A thread that has not yet started is in this state.
* RUNNABLE
  A thread executing in the Java virtual machine is in this state.
* BLOCKED
  A thread that is blocked waiting for a monitor lock is in this state.
* WAITING
  A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
* TIMED_WAITING
  A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
* TERMINATED
  A thread that has exited is in this state.

그러나 3 명의 자식이 완료되기를 기다리는 마스터 스레드를 갖는 것이 더 나은 디자인이라고 생각하고 다른 3 명이 완료되면 마스터는 계속 실행합니다.


3 명의 자녀가 종료되기를 기다리는 것은 사용 패러다임에 맞지 않을 수 있습니다. 다운로드 관리자 인 경우 15 회 다운로드를 시작하고 상태 표시 줄에서 상태를 제거하거나 다운로드가 완료되면 사용자에게 알리면 콜백이 더 잘 작동합니다.
digitaljoel

3

Executors개체를 사용하여 ExecutorService 스레드 풀 을 만들 수도 있습니다. 그런 다음이 invokeAll방법을 사용하여 각 스레드를 실행하고 선물을 검색하십시오. 모든 실행이 완료 될 때까지 차단됩니다. 다른 옵션은 풀을 사용하여 각각을 실행 한 다음 awaitTermination풀 실행이 완료 될 때까지 블록 을 호출 하는 것입니다. shutdown작업 추가가 완료되면 () 로 전화하십시오 .


2

멀티 스레딩 프론트에서 지난 6 년 동안 많은 것들이 변경되었습니다.

join()API 를 사용 하고 잠그는 대신 사용할 수 있습니다.

1. ExecutorService invokeAll() API

주어진 작업을 실행하고 모든 완료시 상태 및 결과를 보유한 선물리스트를 반환합니다.

2. CountDownLatch

하나 이상의 스레드가 다른 스레드에서 수행중인 작업 세트가 완료 될 때까지 대기 할 수 있도록하는 동기화 지원.

CountDownLatch주어진 카운트로 A 가 초기화됩니다. await 메소드는 countDown()메소드 호출로 인해 현재 카운트가 0에 도달 할 때까지 차단되며 , 그 후에 모든 대기 스레드가 해제되고 이후의 await 호출이 즉시 리턴됩니다. 이것은 일회성 현상이므로 카운트를 재설정 할 수 없습니다. 카운트를 재설정하는 버전이 필요한 경우 CyclicBarrier 사용을 고려하십시오.

3. ForkJoinPool 또는 newWorkStealingPool()에서 집행 인은 다른 방법입니다

Future제출에서 모든 작업을 반복 하고 객체 에 대한 ExecutorService호출 get()을 차단하여 상태를 확인하십시오.Future

관련 SE 질문을 살펴보십시오.

자체 스레드를 생성하는 스레드를 기다리는 방법은 무엇입니까?

실행자 : 작업이 재귀 적으로 생성되면 모든 작업이 완료 될 때까지 동기식으로 대기하는 방법은 무엇입니까?


2

Thread 클래스 에 대한 javadoc을 보는 것이 좋습니다 .

스레드 조작을위한 여러 메커니즘이 있습니다.

  • 주 스레드 join()는 세 개의 스레드를 직렬로 연결할 수 있으며, 세 개의 스레드가 모두 완료 될 때까지 진행되지 않습니다.

  • 생성 된 스레드의 스레드 상태를 간격으로여십시오.

  • 별도로 만들어지는 모든 스레드를 넣고 ThreadGroup및 설문 조사 activeCount()상의를 ThreadGroup하고 0에 도착하기를 기다립니다.

  • 스레드 간 통신을위한 사용자 정의 콜백 또는 리스너 유형의 인터페이스를 설정하십시오.

나는 여전히 누락 된 다른 많은 방법이 있다고 확신합니다.


1

간단하고 짧고 이해하기 쉽고 완벽하게 작동하는 솔루션이 있습니다. 다른 스레드가 끝나면 화면을 그려야했습니다. 메인 스레드가 화면을 제어하기 때문에 할 수 없었습니다. 그래서:

(1) 전역 변수를 만들었습니다 boolean end1 = false;. 종료시 스레드가 true로 설정합니다. 이것은 "postDelayed"루프에 의해 메인 스레드에서 선택되어 응답됩니다.

(2) 내 스레드에는 다음이 포함됩니다.

void myThread() {
    end1 = false;
    new CountDownTimer(((60000, 1000) { // milliseconds for onFinish, onTick
        public void onFinish()
        {
            // do stuff here once at end of time.
            end1 = true; // signal that the thread has ended.
        }
        public void onTick(long millisUntilFinished)
        {
          // do stuff here repeatedly.
        }
    }.start();

}

(3) 다행히도 "postDelayed"는 메인 스레드에서 실행되므로 매 초마다 다른 스레드를 확인해야합니다. 다른 스레드가 끝나면 다음에 수행하려는 작업을 시작할 수 있습니다.

Handler h1 = new Handler();

private void checkThread() {
   h1.postDelayed(new Runnable() {
      public void run() {
         if (end1)
            // resond to the second thread ending here.
         else
            h1.postDelayed(this, 1000);
      }
   }, 1000);
}

(4) 마지막으로 다음 코드를 호출하여 코드 어딘가에서 실행하는 모든 것을 시작하십시오.

void startThread()
{
   myThread();
   checkThread();
}

1

가장 쉬운 방법은 ThreadPoolExecutor수업 을 사용하는 것 입니다.

  1. 대기열이 있으며 병렬로 작동 해야하는 스레드 수를 설정할 수 있습니다.
  2. 멋진 콜백 메소드가 있습니다.

후크 방법

이 클래스는 각 작업 실행 전후에 호출 되는 보호 된 재정의 beforeExecute(java.lang.Thread, java.lang.Runnable)afterExecute(java.lang.Runnable, java.lang.Throwable)메서드를 제공합니다 . 이들은 실행 환경을 조작하는 데 사용될 수 있습니다. 예를 들어 ThreadLocals 다시 초기화, 통계 수집 또는 로그 항목 추가 등이 있습니다. 또한 terminated()Executor가 완전히 종료 된 후에 수행해야하는 특수 처리를 수행하기 위해 메소드 를 대체 할 수 있습니다.

정확히 우리가 필요로하는 것입니다. 우리는 우선합니다 afterExecute()각 스레드가 수행되며 우선합니다 후 콜백을 얻기 위해 terminated()모든 스레드가 완료되면 알 수 있습니다.

그래서 여기에 당신이해야 할 일이 있습니다.

  1. 실행기를 작성하십시오.

    private ThreadPoolExecutor executor;
    private int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();    
    
    
    
    private void initExecutor() {
    
    executor = new ThreadPoolExecutor(
            NUMBER_OF_CORES * 2,  //core pool size
            NUMBER_OF_CORES * 2, //max pool size
            60L, //keep aive time
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>()
    ) {
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
                //Yet another thread is finished:
                informUiAboutProgress(executor.getCompletedTaskCount(), listOfUrisToProcess.size());
            }
        }
    
    };
    
        @Override
        protected void terminated() {
            super.terminated();
            informUiThatWeAreDone();
        }
    
    }
  2. 그리고 실을 시작하십시오 :

    private void startTheWork(){
        for (Uri uri : listOfUrisToProcess) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    doSomeHeavyWork(uri);
                }
            });
        }
        executor.shutdown(); //call it when you won't add jobs anymore 
    }

내부 메소드 informUiThatWeAreDone();는 모든 스레드가 완료되면 (예 : UI 업데이트) 필요한 모든 작업을 수행합니다.

참고 :synchronized 작업을 병렬로 수행하고 synchronized다른 synchronized방법 에서 메소드 를 호출하기로 결정한 경우 매우 신중해야하므로 메소드 사용을 잊지 마십시오 ! 이것은 종종 교착 상태로 이어집니다

도움이 되었기를 바랍니다!



0

Thread 클래스의 Java 문서를보십시오. 스레드 상태를 확인할 수 있습니다. 세 개의 스레드를 멤버 변수에 넣으면 세 개의 스레드가 모두 서로의 상태를 읽을 수 있습니다.

그러나 스레드 사이에 경쟁 조건이 발생할 수 있으므로 약간주의해야합니다. 다른 스레드의 상태에 따라 복잡한 논리를 피하십시오. 동일한 변수에 여러 스레드가 쓰는 것을 피하십시오.

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