スレッドから例外をキャッチする方法


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
例外を上位レベルにスローしたい場合はどうすればよいですか?
ロディ2015

6
@rodiはexを、ハンドラーで上位レベルが確認できる揮発性変数(メンバー変数など)に保存します。外部では、nullかどうかを確認し、そうでない場合はスローします。または、新しい揮発性フィールドでUEHを拡張し、そこに例外を保存します。
Ciro Santilli郝海东冠状病六四事件法轮功

1
スレッドを停止せずに、スレッド内から例外をキャッチしたい。これはどういうわけか役に立ちますか?
Lealo 2017

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()も、「メイン」スレッドで例外をキャッチ
テオ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 2017


3

CallableThreadの代わりに使用するFuture#get()と、Callableがスローした例外をスローするcall を実行できます。


1
内部Callable.callでスローされた例外はラップされ、ExcecutionExceptionその原因を評価する必要があることに注意してください。
カールリヒター

3

現在キャッチしているのはRuntimeException、のサブクラスだけですException。ただし、アプリケーションがExceptionの他のサブクラスをスローする場合がありますExceptionに加えてジェネリックをキャッチRuntimeException

スレッドのフロントでは多くの点が変更されているため、高度なJava APIを使用してください。

またはのようなマルチスレッドでは、事前のjava.util.concurrent APIを優先しますExecutorServiceThreadPoolExecutor

例外を処理するようにThreadPoolExecutorをカスタマイズできます。

Oracleのドキュメントページの例:

オーバーライド

protected void afterExecute(Runnable r,
                            Throwable t)

指定されたRunnableの実行が完了すると呼び出されるメソッド。このメソッドは、タスクを実行したスレッドによって呼び出されます。null以外の場合、Throwableは、実行が突然終了する原因となったキャッチされていない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();

上記のコードの上に1つのコンストラクタを追加しました。

 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から:「デフォルトのキャッチされない例外ハンドラーを設定することで、アプリケーションは、キャッチされた例外の処理方法(特定のデバイスやファイルへのロギングなど)を変更できます。システム提供。」

そこで問題の答えが見つかるかもしれません...幸運を祈ります!:-)


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は、エラーをメインスレッドに渡すためのソリューションでもあります。DanCruzのアプローチと同じアプローチです。

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
これはmainメソッドの例外をキャッチしません。
philwb

Threadクラスを拡張しないことを強くお勧めします。OJPCの準備でこれとその理由を読みました。本...推測、彼らは彼らが話していることを知っています
luigi7up

2
"RuntimeException from main"はここに出力されません..例外はメインでキャッチされません
Amrish Pandey

0

スレッドを開始するクラスに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 2015年

0

スレッドでの例外処理:デフォルトでは、run()メソッドは例外をスローしないため、runメソッド内のすべてのチェック済み例外をそこでキャッチして処理するだけでよく、実行時例外の場合はUncaughtExceptionHandlerを使用できます。UncaughtExceptionHandlerは、スレッド実行メソッドで例外を処理するために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

必要人のために、すべてのスレッドがそれらのすべてを実行し、再実行して停止するように、それらのうちのいずれか1つが例外で停止した場合:

@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);

    }

}

総括する :

複数のスレッドを作成するメイン関数があり、それぞれにスレッド内の例外によってトリガーされるUncaughtExceptionHandlerがあります。すべてのスレッドをリストに追加します。UncaughtExceptionHandlerがトリガーされると、リストをループし、すべてのスレッドを停止して、メイン関数の再作成ですべてのスレッドを再起動します。


-5

それは本当に意味がないので、これを行うことはできません。呼び出していないt.join()場合は、tスレッドが例外をスローしたときに、メインスレッドがコードのどこかにある可能性があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.