Як зловити виняток з потоку


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

Використовуйте a 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 збережіть колишню до змінної змінної, яку може бачити верхній рівень у оброблювачі (наприклад, змінної члена). Зовні перевірте, чи немає, інакше киньте. Або розширити UEH новим летючим полем і зберегти там виняток.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
Я хочу зловити виняток зсередини своєї теми - без її зупинки. Чи було б це якось корисно?
Леало

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()чекає чи не блокує, поки потік не закінчить виконання?
Грегор Валентин

@GregorValentin він чекає / блокує, поки нитка не закінчиться Runnable / Callable.
Пітер Лоурі


3

Використовуйте Callableзамість теми, тоді ви можете зателефонувати, Future#get()який кидає будь-який виняток, який кинув Callable.


1
Зауважте, що виняток, кинутий всередину Callable.call, загорнутий у, ExcecutionExceptionі його причину потрібно оцінити.
Карл Ріхтер

3

Наразі ви ловите лише RuntimeExceptionпідклас Exception. Але ваша програма може кинути інші підкласи винятку . Ловити родовий Exceptionна додаток доRuntimeException

Оскільки багато речей було змінено на Threading front, використовуйте розширений API Java.

Віддайте перевагу API java.util.concurrent для багатопотокового типу, як ExecutorServiceабоThreadPoolExecutor .

Ви можете налаштувати свій ThreadPoolExecutor для обробки винятків.

Приклад зі сторінки документації oracle:

Переосмислити

protected void afterExecute(Runnable r,
                            Throwable t)

Спосіб, що застосовується після завершення виконання заданого Runnable. Цей метод викликається потоком, який виконав завдання. Якщо це недійсне значення, Throwable - це невдале RuntimeException або Помилка, що призвело до того, що виконання буде різко припинено.

Приклад коду:

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

Я зіткнувся з тією ж проблемою ... мало роботи (лише для впровадження не анонімних об'єктів) ... ми можемо оголосити об'єкт виключення на рівні класу як нульовий ... потім ініціалізувати його всередині блоку захоплення для методу запуску ... якщо є була помилка в методі запуску, ця змінна не буде нульовою .. ми можемо перевірити нуль для цієї конкретної змінної, і якщо вона не є нульовою, то у виконанні потоку був виняток.

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

перевірка викликуForException () після приєднання ()


1

Чи грали ви з setDefaultUncaughtExceptionHandler () та подібними методами класу Thread? З API: "Встановивши за замовчуванням обробку виключень за умовчанням, програма може змінити спосіб обробки оброблених винятків (наприклад, реєстрація на певному пристрої чи файлі) для тих потоків, які вже приймуть будь-яку поведінку" за замовчуванням " система надана ".

Ви можете знайти відповідь на свою проблему там… удачі! :-)


1

Також з 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();

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.


7
Це не виключає винятку в основному методі.
philwb

Розширення класу Thread сильно не рекомендується. Я прочитав це та пояснення, чому в підготовчому документі OJPC. книга ... Здогадайтесь, вони знають, про що говорять
luigi7up

2
Тут ніколи не друкується "RuntimeException from main". Виняток не вловлюється в основному
Amrish Pandey

0

Якщо ви реалізуєте Thread.UncaughtExceptionHandler у класі, який запускає Threads, ви можете встановити та повторно скинути виняток:

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 () буде синхронізуватися.
NickL

0

Обробка винятків у Thread: Метод run () за замовчуванням не викидає жодного винятку, тому всі перевірені винятки всередині методу run повинні бути спіймані та оброблені лише там, а для виключень під час виконання ми можемо використовувати UncaughtExceptionHandler. UncaughtExceptionHandler - це інтерфейс, що надається Java для обробки виключень у методі запуску теми. Таким чином, ми можемо реалізувати цей інтерфейс і повернути наш клас реалізації назад до об'єкта Thread, використовуючи метод setUncaughtExceptionHandler (). Але цей обробник повинен бути встановлений перед тим, як викликати start () на протектор.

якщо ми не встановимо uncaughtExceptionHandler, то ThreadGrep виконує функції обробника.

 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нитка видає виняток.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.