Jak przechwycić wyjątek z wątku


165

Mam główną klasę Javy, w klasie zaczynam nowy wątek, w głównym czeka, aż wątek umrze. W pewnym momencie wyrzucam wyjątek czasu wykonywania z wątku, ale nie mogę złapać wyjątku wyrzuconego z wątku w głównej klasie.

Oto kod:

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)
    {

    }
  }
}

Czy ktoś wie, dlaczego?

Odpowiedzi:


220

Użyj 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
Co mogę zrobić, jeśli chcę wyrzucić wyjątek na wyższy poziom?
rodi

6
@rodi save ex do zmiennej nietrwałej, którą wyższy poziom może zobaczyć w module obsługi (np. zmienna składowa). Na zewnątrz sprawdź, czy null, w przeciwnym razie rzuć. Lub rozszerz UEH o nowe zmienne pole i zapisz tam wyjątek.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
Chcę wyłapać wyjątek z wnętrza mojego wątku - bez zatrzymywania go. Czy to w jakiś sposób się przyda?
Lealo,

42

Dzieje się tak, ponieważ wyjątki są lokalne dla wątku, a Twój główny wątek w rzeczywistości nie widzi runmetody. Sugeruję, abyś przeczytał więcej o tym, jak działa wątkowanie, ale szybko podsumuj: twoje wezwanie do starturuchomienia innego wątku, całkowicie niezwiązanego z twoim głównym wątkiem. Wezwanie joinpo prostu czeka, aż to się stanie. Wyjątek, który jest rzucany w wątku i nigdy nie przechwycony, kończy go, dlatego joinpowraca do głównego wątku, ale sam wyjątek jest tracony.

Jeśli chcesz być świadomy tych nieprzechwyconych wyjątków, możesz spróbować tego:

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

Więcej informacji na temat obsługi nieprzechwyconych wyjątków można znaleźć tutaj .


Lubię to! Ustawienie procedury obsługi metodą statyczną Thread.setDefaultUncaughtExceptionHandler()również wyłapuje wyjątki w wątku „main”
Teo J.


23

Najprawdopodobniej;

  • nie musisz przekazywać wyjątku z jednego wątku do drugiego.
  • jeśli chcesz obsłużyć wyjątek, po prostu zrób to w wątku, który go wyrzucił.
  • Twój główny wątek nie musi czekać z wątkiem w tle w tym przykładzie, co w rzeczywistości oznacza, że ​​w ogóle nie potrzebujesz wątku w tle.

Jednak załóżmy, że musisz obsłużyć wyjątek z innego wątku podrzędnego. Użyłbym usługi ExecutorService w ten sposób:

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

wydruki

** 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

Ale czy nie future.get()czeka ani nie blokuje, aż wątek zakończy wykonywanie?
Gregor Valentin,

@GregorValentin czeka / blokuje, aż wątek zakończy działanie Runnable / Callable.
Peter Lawrey,


3

Użyj Callablezamiast Thread, możesz wywołać, Future#get()który zgłosi każdy wyjątek, który wyrzucił wywołanie .


1
Zwróć uwagę, że wyjątek zgłoszony wewnątrz Callable.calljest opakowany w element ExcecutionExceptioni należy ocenić jego przyczynę.
Karl Richter

3

Obecnie łapiesz tylko RuntimeExceptionpodklasę Exception. Jednak Twoja aplikacja może generować inne podklasy Exception . Złap generic ExceptionopróczRuntimeException

Ponieważ wiele rzeczy zostało zmienionych na froncie wątkowania, użyj zaawansowanego API java.

Preferuj zaawansowane API java.util.concurrent do obsługi wielu wątków, takich jak ExecutorServicelub ThreadPoolExecutor.

Możesz dostosować ThreadPoolExecutor do obsługi wyjątków.

Przykład ze strony dokumentacji oracle:

Nadpisanie

protected void afterExecute(Runnable r,
                            Throwable t)

Metoda wywoływana po zakończeniu wykonywania danego Runnable. Ta metoda jest wywoływana przez wątek, który wykonał zadanie. Jeśli wartość inna niż null, Throwable jest nieprzechwyconym RuntimeException lub Error, który spowodował nagłe zakończenie wykonywania.

Przykładowy kod:

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

Stosowanie:

ExtendedExecutor service = new ExtendedExecutor();

Dodałem jeden konstruktor do powyższego kodu jako:

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

Możesz zmienić ten konstruktor, aby odpowiadał Twoim wymaganiom dotyczącym liczby wątków.

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

2

Napotkałem ten sam problem ... niewielkie obejście (tylko w przypadku implementacji, a nie obiektów anonimowych) ... możemy zadeklarować obiekt wyjątku na poziomie klasy jako null ... następnie zainicjować go w bloku catch dla metody uruchamiania ... jeśli istnieje wystąpił błąd w metodzie uruchamiania, ta zmienna nie będzie zerowa .. możemy wtedy sprawdzić wartość null dla tej konkretnej zmiennej, a jeśli nie jest pusta, wtedy wystąpił wyjątek w wykonaniu wątku.

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

wywołanie checkForException () po złączeniu ()


1

Czy bawiłeś się z setDefaultUncaughtExceptionHandler () i podobnymi metodami klasy Thread? Z interfejsu API: „Ustawiając domyślną procedurę obsługi nieprzechwyconych wyjątków, aplikacja może zmienić sposób obsługi nieprzechwyconych wyjątków (np. Logowanie do określonego urządzenia lub pliku) dla tych wątków, które już zaakceptowałyby jakiekolwiek„ domyślne ”zachowanie dostarczony system. "

Możesz tam znaleźć odpowiedź na swój problem ... powodzenia! :-)


1

Również z poziomu Java 8 możesz napisać odpowiedź Dan Cruz jako:

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 to także rozwiązanie umożliwiające przekazanie błędu do głównego wątku. Jest to takie samo podejście jak podejście Dana Cruza.

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

Jedyną zmianą jest to, że zamiast tworzyć zmienną zmienną, możesz użyć AtomicReference, który zrobił to samo za kulisami.


0

Przedłużanie prawie zawsze jest złe Thread. Nie mogę tego powiedzieć wystarczająco mocno.

Zasada wielowątkowości nr 1: Rozszerzanie Threadjest nieprawidłowe. *

Jeśli Runnablezamiast tego zaimplementujesz , zobaczysz swoje oczekiwane zachowanie.

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) {

    }
  }
}

produkuje;

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)

* chyba że chcesz zmienić sposób, w jaki Twoja aplikacja używa wątków, co w 99,9% przypadków tego nie robi. Jeśli uważasz, że znajdujesz się w 0,1% przypadków, zapoznaj się z zasadą nr 1.


7
To nie wychwytuje wyjątku w głównej metodzie.
philwb

Rozszerzanie klasy Thread jest zdecydowanie odradzane. Przeczytałem to i wyjaśnienie, dlaczego w przygotowaniu OJPC. książka ... Chyba wiedzą, o czym mówią
luigi7up,

2
„RuntimeException from main” nigdy nie jest tutaj drukowany .. wyjątek nie jest przechwytywany w main
Amrish Pandey

0

Jeśli zaimplementujesz Thread.UncaughtExceptionHandler w klasie, która uruchamia Threads, możesz ustawić, a następnie ponownie zgłosić wyjątek:

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

}

Co powoduje następujący wynik:

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)

Nie ma potrzeby, aby funkcja Throwable initException była niestabilna, ponieważ t.join () będzie synchronizować.
NickL

0

Obsługa wyjątków w Thread: Domyślnie metoda run () nie zgłasza żadnego wyjątku, więc wszystkie sprawdzane wyjątki wewnątrz metody run muszą być wychwycone i obsługiwane tylko tam, a dla wyjątków środowiska uruchomieniowego możemy użyć UncaughtExceptionHandler. UncaughtExceptionHandler to interfejs udostępniany przez Javę do obsługi wyjątków w metodzie wykonywania wątków. Możemy więc zaimplementować ten interfejs i przywrócić naszą klasę implementującą z powrotem do obiektu Thread za pomocą metody setUncaughtExceptionHandler (). Ale ten program obsługi musi być ustawiony przed wywołaniem start () na bieżniku.

Jeśli nie ustawimy uncaughtExceptionHandler, ThreadGroup będzie działać jako program obsługi.

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

Ładne wyjaśnienie podane na http://coder2design.com/thread-creation/#exceptions


0

Moje rozwiązanie z 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

Dla tych, którzy muszą zatrzymać wszystkie uruchomione wątki i ponownie uruchomić je wszystkie, gdy którykolwiek z nich zostanie zatrzymany na wyjątku:

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

    }

}

Podsumowując:

Masz główną funkcję, która tworzy wiele wątków, każdy z nich ma UncaughtExceptionHandler, który jest wyzwalany przez dowolny wyjątek wewnątrz wątku. Dodajesz każdy wątek do listy. Jeśli zostanie wyzwolony UncaughtExceptionHandler, przejdzie w pętlę przez List, zatrzyma każdy wątek i ponownie uruchomi główną funkcję odtwarzającą cały Thread.


-5

Nie możesz tego zrobić, ponieważ to naprawdę nie ma sensu. Jeśli nie wywołałeś t.join(), główny wątek może znajdować się w dowolnym miejscu kodu, gdy twątek zgłosi wyjątek.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.