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

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
Что я могу сделать, если я хочу выбросить исключение на верхний уровень?
Роди
6
@rodi сохранить ex в переменную, которую верхний уровень может видеть в обработчике (например, переменная-член). Снаружи, проверьте, если ноль, иначе бросьте. Или добавьте UEH с новым изменяемым полем и сохраните там исключение.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
1
Я хочу поймать исключение из моей ветки - без остановки. Будет ли это как-то полезно?
Лило
42

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

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

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

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

abyx
источник
Мне нравится это! Установка обработчика статическим методом 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()ждет или не блокирует, пока поток не закончил выполнение?
Грегор Валентин
@GregorValentin это ждет / блокирует, пока поток не закончил Runnable / Callable.
Питер Лори
3

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

artbristol
источник
1
Обратите внимание, что выброшенное исключение Callable.callобернуто в ExcecutionExceptionи его причина должна быть оценена.
Карл Рихтер
3

В настоящее время вы ловите только 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>);
Равиндра Бабу
источник
2

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

Java для начинающих
источник
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();
Andr1i
источник
1

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

Единственное изменение состоит в том, что вместо создания изменчивой переменной вы можете использовать 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.

Qwerky
источник
7
Это не ловит исключение в основном методе.
Philwb
Расширять класс Thread настоятельно не рекомендуется. Я прочитал это и объяснение, почему в подготовке OJPC. книга ... Думаю, они знают, о чем говорят
luigi7up
2
«RuntimeException from main» здесь никогда не печатается .. исключение не перехватывается в 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)
Стефано
источник
Нет необходимости делать Throwable initException энергозависимым, поскольку t.join () будет синхронизироваться.
Ник
0

Обработка исключений в потоке: по умолчанию метод 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/#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);

    }

}

Подводить итоги :

У вас есть основная функция, которая создает несколько потоков, каждый из которых имеет UncaughtExceptionHandler, который вызывается любым исключением внутри потока. Вы добавляете каждую тему в список. Если UncaughtExceptionHandler является триггером, он будет циклически проходить по списку, останавливать каждый поток и перезапускать основную функцию, воссоздающую весь поток.

Антуан Вулкан
источник
-5

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

Матиас Шварц
источник