Как поймать исключение из потока

У меня есть основной класс 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)
    {

    }
  }
}

Кто-нибудь знает почему?

14 ответов

Решение

Использовать Thread.UncaughtExceptionHandler,

Thread.UncaughtExceptionHandler h = new Thread.UncaughtExceptionHandler() {
    public void uncaughtException(Thread th, Throwable ex) {
        System.out.println("Uncaught exception: " + ex);
    }
};
Thread t = new Thread() {
    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();

Это потому, что исключения являются локальными для потока, а ваш основной поток на самом деле не видит run метод. Я предлагаю вам прочитать больше о том, как работает многопоточность, но чтобы быстро подвести итог: ваш призыв к start запускает другую ветку, совершенно не связанную с вашей основной веткой. Призыв к join просто ждет, когда это будет сделано. Исключение, которое выбрасывается в поток и никогда не перехватывается, завершает его, поэтому join возвращается в ваш основной поток, но само исключение теряется.

Если вы хотите знать об этих необученных исключениях, вы можете попробовать это:

Thread.setDefaultExceptionHandler(new UncaughtExceptionHandler() {
    public void unchaughtException(Thread t, Throwable e) {
        System.out.println("Caught " + e);
    }
});

Более подробную информацию об обработке необработанных исключений можно найти здесь.

Это объясняет переход состояния потоков в зависимости от того, возникли исключения или нет:

Потоки и обработка исключений

Более вероятный;

  • вам не нужно передавать исключение из одного потока в другой.
  • если вы хотите обработать исключение, просто сделайте это в потоке, который его выбросил.
  • ваш основной поток не должен ждать от фонового потока в этом примере, что фактически означает, что вам вообще не нужен фоновый поток.

Однако предположим, что вам нужно обработать исключение из дочернего потока другого. Я хотел бы использовать 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

Пожалуйста, взгляните на Thread.UncaughtExceptionHandler

Лучший (альтернативный) способ - использовать Callable и Future, чтобы получить одинаковый результат...

Использование Callable вместо темы, тогда вы можете позвонить Future#get() который бросает любое исключение, которое бросило Callable.

AtomicReference также является решением для передачи ошибки в основной поток. Это тот же подход, что и у Дэна Круза.

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

The only change is that instead of creating a volatile variable you can use AtomicReference which did same thing behind the scenes.

В настоящее время вы ловите только RuntimeExceptionподкласс Exception, Но ваше приложение может выдавать другие подклассы исключения. Поймать универсальный Exception в дополнение к RuntimeException

Поскольку многие вещи были изменены на фронте Threading, используйте расширенный API Java.

Предпочитаю предварительный API java.util.concurrent для многопоточности, например ExecutorService или же ThreadPoolExecutor,

Вы можете настроить свой ThreadPoolExecutor для обработки исключений.

Пример со страницы документации оракула:

Override

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

Я добавил один конструктор поверх кода выше:

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

Вы можете изменить этот конструктор в соответствии с вашими требованиями по количеству потоков.

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

Также из Java 8 вы можете написать ответ Дэна Круза как:

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

Я столкнулся с той же проблемой... немного обойти (только для реализации не анонимных объектов) ... мы можем объявить объект исключения уровня класса как нулевой... затем инициализировать его внутри блока catch для метода run... если есть была ошибка в методе run, эта переменная не будет иметь значение 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;
            }
        }
}     

вызовите checkForException() после join()

Играли ли вы с setDefaultUncaughtExceptionHandler() и схожими методами класса Thread? Из API: "Установив обработчик неперехваченных исключений по умолчанию, приложение может изменить способ обработки неперехваченных исключений (например, запись на определенное устройство или файл) для тех потоков, которые уже приняли бы любое поведение" по умолчанию ", система обеспечена."

Вы можете найти ответ на свой вопрос там... удачи!:-)

Обработка исключений в потоке: по умолчанию метод run() не выдает никаких исключений, поэтому все проверенные исключения внутри метода run должны быть перехвачены и обработаны только там, а для исключений времени выполнения мы можем использовать UncaughtExceptionHandler. UncaughtExceptionHandler - это интерфейс, предоставляемый Java для обработки исключений в методе потока выполнения. Таким образом, мы можем реализовать этот интерфейс и вернуть наш реализующий класс обратно в объект Thread, используя метод setUncaughtExceptionHandler(). Но этот обработчик должен быть установлен до того, как мы вызовем start() на шаге.

если мы не установим uncaughtExceptionHandler, то 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/

Если вы реализуете 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)

Почти всегда неправильно расширять 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.

Мое решение с 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.");
}

Для тех, кому нужно остановить все потоки и повторно запустить их все, когда любой из них остановлен из-за исключения:

@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 является триггером, он будет перебирать список, останавливать каждый поток и перезапускать основную функцию, воссоздавая весь поток.

Вы не можете сделать это, так как это не имеет смысла. Если бы ты не звонил t.join() тогда ваш основной поток может быть где угодно в коде, когда t поток создает исключение.

Другие вопросы по тегам