스레드에서 예외를 잡는 방법


165

Java 메인 클래스가 있습니다. 클래스에서 새 스레드를 시작합니다. 메인에서 스레드가 죽을 때까지 기다립니다. 언젠가 스레드에서 런타임 예외가 발생하지만 주 클래스의 스레드에서 발생한 예외를 잡을 수 없습니다.

코드는 다음과 같습니다.

public class Test extends Thread
{
  public static void main(String[] args) throws InterruptedException
  {
    Test t = new Test();

    try
    {
      t.start();
      t.join();
    }
    catch(RuntimeException e)
    {
      System.out.println("** RuntimeException from main");
    }

    System.out.println("Main stoped");
  }

  @Override
  public void run()
  {
    try
    {
      while(true)
      {
        System.out.println("** Started");

        sleep(2000);

        throw new RuntimeException("exception from thread");
      }
    }
    catch (RuntimeException e)
    {
      System.out.println("** RuntimeException from thread");

      throw e;
    } 
    catch (InterruptedException e)
    {

    }
  }
}

왜 그 이유를 아는 사람이 있습니까?

답변:


220

를 사용하십시오 Thread.UncaughtExceptionHandler.

Thread.UncaughtExceptionHandler h = new Thread.UncaughtExceptionHandler() {
    @Override
    public void uncaughtException(Thread th, Throwable ex) {
        System.out.println("Uncaught exception: " + ex);
    }
};
Thread t = new Thread() {
    @Override
    public void run() {
        System.out.println("Sleeping ...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.println("Interrupted.");
        }
        System.out.println("Throwing exception ...");
        throw new RuntimeException();
    }
};
t.setUncaughtExceptionHandler(h);
t.start();

13
예외를 상위 수준으로 던지려면 어떻게해야합니까?
rodi

6
@rodi ex를 핸들러에서 볼 수있는 휘발성 변수 (예 : 멤버 변수)에 ex를 저장합니다. 외부에서는 null인지 확인하고 그렇지 않으면 던집니다. 또는 새로운 휘발성 필드로 UEH를 확장하고 예외를 저장하십시오.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
스레드가 멈추지 않고 스레드 내부에서 예외를 잡고 싶습니다. 어떻게 든 사용됩니까?
Lealo

42

예외는 스레드에 국한되며 메인 스레드는 실제로 run메소드를 보지 않기 때문 입니다. 스레딩의 작동 방식에 대해 자세히 읽어 보지만 요약하면 start주 스레드와 전혀 관련이없는 다른 스레드 를 시작 하라는 요청 이 있습니다. 호출은 join단순히 완료되기를 기다립니다. 스레드에서 발생하고 포착되지 않은 예외는 종료되므로 join주 스레드에서 반환되지만 예외 자체는 손실됩니다.

이러한 잡히지 않은 예외를 알고 싶다면 다음을 시도하십시오.

Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("Caught " + e);
    }
});

포착되지 않은 예외 처리에 대한 자세한 내용은 여기를 참조하십시오 .


나는 그것을 좋아한다! 정적 메소드를 사용하여 핸들러를 설정하면 Thread.setDefaultUncaughtExceptionHandler()스레드 "main"의 예외도 발견됩니다.
Teo J.


23

가능성이 높습니다.

  • 한 스레드에서 다른 스레드로 예외를 전달할 필요는 없습니다.
  • 예외를 처리하려면 스레드에서 예외를 처리하십시오.
  • 이 예제에서 기본 스레드는 백그라운드 스레드에서 기다릴 필요가 없습니다. 실제로 백그라운드 스레드가 전혀 필요하지 않습니다.

그러나 자식 스레드에서 예외를 처리해야한다고 가정 해보십시오. 다음과 같이 ExecutorService를 사용합니다.

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Void> future = executor.submit(new Callable<Void>() {
    @Override
    public Void call() throws Exception {
        System.out.println("** Started");
        Thread.sleep(2000);
        throw new IllegalStateException("exception from thread");
    }
});
try {
    future.get(); // raises ExecutionException for any uncaught exception in child
} catch (ExecutionException e) {
    System.out.println("** RuntimeException from thread ");
    e.getCause().printStackTrace(System.out);
}
executor.shutdown();
System.out.println("** Main stopped");

인쇄물

** Started
** RuntimeException from thread 
java.lang.IllegalStateException: exception from thread
    at Main$1.call(Main.java:11)
    at Main$1.call(Main.java:6)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
    at java.util.concurrent.FutureTask.run(FutureTask.java:138)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:662)
** Main stopped

그러나 future.get()스레드가 실행을 마칠 때까지 기다리거나 차단 하지 않습니까?
Gregor Valentin

@GregorValentin 스레드가 Runnable / Callable을 완료 할 때까지 대기 / 차단합니다.
Peter Lawrey


3

사용하여 Callable다음 호출 할 수 있습니다, 대신 스레드의 Future#get()Callable를 던진 것을 어떤 예외가 발생한다.


1
내부 Callable.call에 던져진 예외 는에 싸여 ExcecutionException있고 그 원인을 평가해야합니다.
Karl Richter

3

현재, 당신은 RuntimeException하위 클래스의 잡기 만하고 있습니다 Exception. 그러나 응용 프로그램은 Exception의 다른 하위 클래스를 던질 수 있습니다 . Exception이외에도 일반 잡기RuntimeException

스레딩 프론트에서 많은 것들이 변경되었으므로 고급 자바 API를 사용하십시오.

사전 java.util.concurrent에서의 선호 API를 같은 멀티 스레딩을 위해 ExecutorService또는 ThreadPoolExecutor.

예외를 처리하도록 ThreadPoolExecutor 를 사용자 정의 할 수 있습니다 .

오라클 문서 페이지의 예 :

우세하다

protected void afterExecute(Runnable r,
                            Throwable t)

지정된 Runnable의 실행이 완료되면 호출 된 메소드입니다. 이 메소드는 태스크를 실행 한 스레드에 의해 호출됩니다. 널이 아닌 경우 Throwable은 catch되지 않은 RuntimeException 또는 Error로 인해 실행이 갑자기 종료됩니다.

예제 코드 :

class ExtendedExecutor extends ThreadPoolExecutor {
   // ...
   protected void afterExecute(Runnable r, Throwable t) {
     super.afterExecute(r, t);
     if (t == null && r instanceof Future<?>) {
       try {
         Object result = ((Future<?>) r).get();
       } catch (CancellationException ce) {
           t = ce;
       } catch (ExecutionException ee) {
           t = ee.getCause();
       } catch (InterruptedException ie) {
           Thread.currentThread().interrupt(); // ignore/reset
       }
     }
     if (t != null)
       System.out.println(t);
   }
 }

용법:

ExtendedExecutor service = new ExtendedExecutor();

위 코드 위에 생성자를 하나 추가했습니다.

 public ExtendedExecutor() { 
       super(1,5,60,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(100));
   }

스레드 수에 대한 요구 사항에 맞게이 생성자를 변경할 수 있습니다.

ExtendedExecutor service = new ExtendedExecutor();
service.submit(<your Callable or Runnable implementation>);

2

나는 같은 문제에 직면했다 ... 거의 해결 방법 (익명 객체가 아닌 구현에만 해당) ... 클래스 레벨 예외 객체를 null로 선언 할 수 있습니다 ... 그런 다음 run 메소드를 위해 catch 블록 내에서 초기화하십시오 ... run 메소드에서 오류가 발생했습니다.이 변수는 null이 아닙니다.이 특정 변수에 대해 null 검사를 할 수 있으며 null이 아닌 경우 스레드 실행 내부에 예외가 있습니다.

class TestClass implements Runnable{
    private Exception ex;

        @Override
        public void run() {
            try{
                //business code
               }catch(Exception e){
                   ex=e;
               }
          }

      public void checkForException() throws Exception {
            if (ex!= null) {
                throw ex;
            }
        }
}     

join () 후 checkForException () 호출


1

setDefaultUncaughtExceptionHandler () 및 Thread 클래스의 유사한 메소드를 가지고 놀았습니까? API에서 : "기본 catch되지 않은 예외 처리기를 설정하면 응용 프로그램은"기본 "동작을 이미 받아들이는 스레드에 대해 catch되지 않은 예외가 처리되는 방식 (예 : 특정 장치 또는 파일에 로깅)을 변경할 수 있습니다. 시스템 제공. "

거기에서 문제에 대한 답을 찾을 수 있습니다 ... 행운을 빕니다! :-)


1

또한 Java 8에서 Dan Cruz 답변을 다음과 같이 작성할 수 있습니다.

Thread t = new Thread(()->{
            System.out.println("Sleeping ...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Interrupted.");
            }
            System.out.println("Throwing exception ...");
            throw new RuntimeException(); });


t.setUncaughtExceptionHandler((th, ex)-> log(String.format("Exception in thread %d id: %s", th.getId(), ex)));
t.start();

1

AtomicReference는 오류를 메인 스레드에 전달하는 솔루션입니다. Dan Cruz와 같은 접근 방식입니다.

AtomicReference<Throwable> errorReference = new AtomicReference<>();

    Thread thread = new Thread() {
        public void run() {
            throw new RuntimeException("TEST EXCEPTION");

        }
    };
    thread.setUncaughtExceptionHandler((th, ex) -> {
        errorReference.set(ex);
    });
    thread.start();
    thread.join();
    Throwable newThreadError= errorReference.get();
    if (newThreadError!= null) {
        throw newThreadError;
    }  

유일한 변화는 휘발성 변수를 생성하는 대신 AtomicReference를 사용하여 장면 뒤에서 동일한 작업을 수행 할 수 있다는 것입니다.


0

확장하는 것은 거의 항상 잘못 Thread입니다. 나는 이것을 충분히 강하게 진술 할 수 없다.

멀티 스레딩 규칙 # 1 : 확장 Thread이 잘못되었습니다. *

Runnable대신 구현 하면 예상되는 동작을 볼 수 있습니다.

public class Test implements Runnable {

  public static void main(String[] args) {
    Test t = new Test();
    try {
      new Thread(t).start();
    } catch (RuntimeException e) {
      System.out.println("** RuntimeException from main");
    }

    System.out.println("Main stoped");

  }

  @Override
  public void run() {
    try {
      while (true) {
        System.out.println("** Started");

        Thread.sleep(2000);

        throw new RuntimeException("exception from thread");
      }
    } catch (RuntimeException e) {
      System.out.println("** RuntimeException from thread");
      throw e;
    } catch (InterruptedException e) {

    }
  }
}

생산;

Main stoped
** Started
** RuntimeException from threadException in thread "Thread-0" java.lang.RuntimeException: exception from thread
    at Test.run(Test.java:23)
    at java.lang.Thread.run(Thread.java:619)

* 애플리케이션이 스레드를 사용하는 방식을 변경하지 않는 한 (99.9 %는 그렇지 않음) 귀하가 0.1 %의 사례에 있다고 생각되면 규칙 # 1을 참조하십시오.


7
이것은 주요 방법에서 예외를 포착하지 않습니다.
philwb

Thread 클래스를 확장하지 않는 것이 좋습니다. OJPC 준비 과정에서이 내용과 설명을 읽었습니다. 책 ... 추측, 그들은 그들이 무슨 말을하는지 알고
luigi7up

2
"주에서의 RuntimeException은"예외가 메인에 걸려 있지 않습니다 .. 여기에 인쇄되지 않습니다
Amrish 펜디 교수를

0

Threads를 시작하는 클래스에서 Thread.UncaughtExceptionHandler를 구현하면 예외를 설정 한 다음 다시 던질 수 있습니다.

public final class ThreadStarter implements Thread.UncaughtExceptionHandler{

private volatile Throwable initException;

    public void doSomeInit(){
        Thread t = new Thread(){
            @Override
            public void run() {
              throw new RuntimeException("UNCAUGHT");
            }
        };
        t.setUncaughtExceptionHandler(this);

        t.start();
        t.join();

        if (initException != null){
            throw new RuntimeException(initException);
        }

    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        initException =  e;
    }    

}

다음과 같은 결과가 발생합니다.

Exception in thread "main" java.lang.RuntimeException: java.lang.RuntimeException: UNCAUGHT
    at com.gs.gss.ccsp.enrichments.ThreadStarter.doSomeInit(ThreadStarter.java:24)
    at com.gs.gss.ccsp.enrichments.ThreadStarter.main(ThreadStarter.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: java.lang.RuntimeException: UNCAUGHT
    at com.gs.gss.ccsp.enrichments.ThreadStarter$1.run(ThreadStarter.java:15)

t.join ()이 동기화되므로 Throwable initException을 휘발성으로 만들 필요가 없습니다.
NickL

0

스레드에서 예외 처리 : 기본적으로 run () 메소드는 예외를 발생시키지 않으므로 run 메소드 내에서 확인 된 모든 예외를 포착하고 처리해야하며 런타임 예외의 경우 UncaughtExceptionHandler를 사용할 수 있습니다. UncaughtExceptionHandler는 Thread run 메소드에서 예외를 처리하기 위해 Java에서 제공하는 인터페이스입니다. 따라서이 인터페이스를 구현하고 setUncaughtExceptionHandler () 메소드를 사용하여 구현 클래스를 Thread 객체로 되돌릴 수 있습니다. 그러나이 핸들러는 트레드에서 start ()를 호출하기 전에 설정해야합니다.

uncaughtExceptionHandler를 설정하지 않으면 Threads ThreadGroup이 핸들러 역할을합니다.

 public class FirstThread extends Thread {

int count = 0;

@Override
public void run() {
    while (true) {
        System.out.println("FirstThread doing something urgent, count : "
                + (count++));
        throw new RuntimeException();
    }

}

public static void main(String[] args) {
    FirstThread t1 = new FirstThread();
    t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
        public void uncaughtException(Thread t, Throwable e) {
            System.out.printf("Exception thrown by %s with id : %d",
                    t.getName(), t.getId());
            System.out.println("\n"+e.getClass());
        }
    });
    t1.start();
}
}

http://coder2design.com/thread-creation/#exceptions에 대한 좋은 설명


0

RxJava를 사용한 솔루션 :

@Test(expectedExceptions = TestException.class)
public void testGetNonexistentEntry() throws Exception
{
    // using this to work around the limitation where the errors in onError (in subscribe method)
    // cannot be thrown out to the main thread
    AtomicReference<Exception> ex = new AtomicReference<>();
    URI id = getRandomUri();
    canonicalMedia.setId(id);

    client.get(id.toString())
        .subscribe(
            m ->
                fail("Should not be successful"),
            e ->
                ex.set(new TestException()));

    for(int i = 0; i < 5; ++i)
    {
        if(ex.get() != null)
            throw ex.get();
        else
            Thread.sleep(1000);
    }
    Assert.fail("Cannot find the exception to throw.");
}

0

모든 스레드 실행을 중지 하고 예외 중 하나가 중지되면 모든 스레드를 다시 실행 해야하는 사람들의 경우 :

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {

     // could be any function
     getStockHistory();

}


public void getStockHistory() {

     // fill a list of symbol to be scrapped
     List<String> symbolListNYSE = stockEntityRepository
     .findByExchangeShortNameOnlySymbol(ContextRefreshExecutor.NYSE);


    storeSymbolList(symbolListNYSE, ContextRefreshExecutor.NYSE);

}


private void storeSymbolList(List<String> symbolList, String exchange) {

    int total = symbolList.size();

    // I create a list of Thread 
    List<Thread> listThread = new ArrayList<Thread>();

    // For each 1000 element of my scrapping ticker list I create a new Thread
    for (int i = 0; i <= total; i += 1000) {
        int l = i;

        Thread t1 = new Thread() {

            public void run() {

                // just a service that store in DB my ticker list
                storingService.getAndStoreStockPrice(symbolList, l, 1000, 
                MULTIPLE_STOCK_FILL, exchange);

            }

        };

    Thread.UncaughtExceptionHandler h = new Thread.UncaughtExceptionHandler() {
            public void uncaughtException(Thread thread, Throwable exception) {

                // stop thread if still running
                thread.interrupt();

                // go over every thread running and stop every one of them
                listThread.stream().forEach(tread -> tread.interrupt());

                // relaunch all the Thread via the main function
                getStockHistory();
            }
        };

        t1.start();
        t1.setUncaughtExceptionHandler(h);

        listThread.add(t1);

    }

}

요약하면 :

다중 스레드를 작성하는 기본 기능이 있으며 각 스레드에는 스레드 내부의 Exception에 의해 트리거되는 UncaughtExceptionHandler가 있습니다. 모든 스레드를 목록에 추가합니다. UncaughtExceptionHandler가 트리거되면 List를 반복하고 모든 스레드를 중지하고 모든 스레드에서 기본 함수 재생을 다시 시작합니다.


-5

실제로 의미가 없기 때문에이 작업을 수행 할 수 없습니다. 호출하지 않은 경우 스레드에서 예외가 발생 t.join()하면 기본 스레드가 코드의 아무 곳에 나있을 수 있습니다 t.

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