Come catturare un'eccezione da un thread


165

Ho una classe principale Java, nella classe, inizio un nuovo thread, nella maggior parte dei casi attende che il thread muoia. Ad un certo momento, lancio un'eccezione di runtime dal thread, ma non riesco a rilevare l'eccezione generata dal thread nella classe principale.

Ecco il codice:

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

    }
  }
}

Qualcuno sa perché?

Risposte:


220

Usa 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
Cosa posso fare se voglio passare l'eccezione a un livello superiore?
rodi,

6
@rodi salva ex in una variabile volatile che il livello superiore può vedere nel gestore (ad es. variabile membro). All'esterno, controlla se è nullo, altrimenti lancia. Oppure estendi UEH con un nuovo campo volatile e memorizza l'eccezione lì.
Ciro Santilli 19 冠状 病 六四 事件 法轮功

1
Voglio catturare un'eccezione dall'interno del mio thread, senza che venga arrestato. Questo sarebbe in qualche modo utile?
Lealo,

42

Questo perché le eccezioni sono locali in un thread e il thread principale in realtà non vede il runmetodo. Ti suggerisco di leggere di più su come funziona il threading, ma per riassumere rapidamente: la tua chiamata per startavviare un thread diverso, totalmente non correlata al thread principale. La chiamata ad joinaspettare semplicemente che sia fatta. Un'eccezione che viene generata in un thread e che non viene mai rilevata la termina, motivo per cui joinritorna sul thread principale, ma l'eccezione stessa viene persa.

Se vuoi essere consapevole di queste eccezioni non rilevate, puoi provare questo:

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

Ulteriori informazioni sulla gestione delle eccezioni non rilevate sono disponibili qui .


Mi piace! L'impostazione del gestore con il metodo statico rileva Thread.setDefaultUncaughtExceptionHandler()anche le eccezioni nel thread "main"
Teo J.,


23

Più probabilmente;

  • non è necessario passare l'eccezione da un thread a un altro.
  • se vuoi gestire un'eccezione, fallo nel thread che l'ha lanciata.
  • il tuo thread principale non ha bisogno di attendere dal thread in background in questo esempio, il che significa che in realtà non hai bisogno di un thread in background.

Tuttavia, supponiamo che sia necessario gestire un'eccezione da un thread figlio a un altro. Vorrei usare un ExecutorService in questo modo:

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

stampe

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

Ma non future.get()aspetti o blocchi fino al termine dell'esecuzione del thread?
Gregor Valentin,

@GregorValentin attende / blocca fino a quando il thread ha terminato Runnable / Callable.
Peter Lawrey,


3

Usa Callableinvece di Thread, quindi puoi chiamare Future#get()che genera qualsiasi eccezione lanciata dalla Callable.


1
Si noti che l'eccezione generata all'interno Callable.callè racchiusa in un ExcecutionExceptione la sua causa deve essere valutata.
Karl Richter,

3

Attualmente stai catturando solo RuntimeExceptionuna sottoclasse di Exception. Ma l'applicazione potrebbe generare altre sottoclassi di eccezione . Cattura generico Exceptionoltre aRuntimeException

Poiché molte cose sono state modificate sul threading front, utilizzare l'API Java avanzata.

Preferisci l' API java.util.concurrent anticipata per multi-thread come ExecutorServiceoThreadPoolExecutor .

Puoi personalizzare ThreadPoolExecutor per gestire le eccezioni.

Esempio dalla pagina della documentazione di Oracle:

Oltrepassare

protected void afterExecute(Runnable r,
                            Throwable t)

Metodo invocato al completamento dell'esecuzione del Runnable specificato. Questo metodo è invocato dal thread che ha eseguito l'attività. Se non nullo, Throwable è l'eccezione o l'errore RuntimeExught non rilevati che ha causato la chiusura improvvisa dell'esecuzione.

Codice di esempio:

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

Uso:

ExtendedExecutor service = new ExtendedExecutor();

Ho aggiunto un costruttore sopra il codice sopra come:

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

È possibile modificare questo costruttore in base alle proprie esigenze sul numero di thread.

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

2

Ho affrontato lo stesso problema ... poca soluzione (solo per l'implementazione non di oggetti anonimi) ... possiamo dichiarare null l'eccezione a livello di classe ... quindi inizializzarla all'interno del blocco catch per il metodo run ... è stato un errore nel metodo di esecuzione, questa variabile non sarà nulla .. possiamo quindi avere un controllo null per questa particolare variabile e se non è null allora c'era un'eccezione nell'esecuzione del thread.

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

call checkForException () dopo join ()


1

Hai giocato con setDefaultUncaughtExceptionHandler () e con i metodi simili della classe Thread? Dall'API: "Impostando il gestore eccezioni non rilevate predefinito, un'applicazione può cambiare il modo in cui vengono gestite le eccezioni non rilevate (come la registrazione su un dispositivo o file specifico) per quei thread che accetterebbero già qualsiasi comportamento" predefinito " sistema fornito. "

Lì potresti trovare la risposta al tuo problema ... buona fortuna! :-)


1

Inoltre da Java 8 puoi scrivere la risposta di Dan Cruz come:

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 è anche una soluzione per passare l'errore al thread principale. È lo stesso approccio di quello di Dan Cruz.

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

L'unica modifica è che invece di creare una variabile volatile puoi usare AtomicReference che ha fatto la stessa cosa dietro le quinte.


0

È quasi sempre sbagliato estenderlo Thread. Non posso affermarlo abbastanza fortemente.

Regola n. 1 del multithreading: l'estensione Threadè errata. *

Se Runnableinvece implementi , vedrai il tuo comportamento previsto.

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

    }
  }
}

produce;

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)

* a meno che non si desideri modificare il modo in cui l'applicazione utilizza i thread, cosa che nel 99,9% dei casi non è possibile. Se ritieni di essere nello 0,1% dei casi, consulta la regola n. 1.


7
Questo non cattura l'eccezione nel metodo principale.
philwb,

L'estensione della classe Thread è fortemente sconsigliata. Ho letto questo e la spiegazione del perché nella preparazione di OJPC. libro ... Indovina, sanno di cosa stanno parlando
luigi7up

2
"RuntimeException from main" non viene mai stampato qui .. l'eccezione non viene rilevata in main
Amrish Pandey,

0

Se si implementa Thread.UncaughtExceptionHandler nella classe che avvia i thread, è possibile impostare e quindi ripetere l'eccezione:

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

}

Che provoca il seguente output:

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)

Non è necessario rendere instabile Throwable initException, poiché t.join () si sincronizzerà.
NickL

0

Gestione delle eccezioni nel thread: per impostazione predefinita il metodo run () non genera alcuna eccezione, quindi tutte le eccezioni verificate all'interno del metodo run devono essere rilevate e gestite solo lì e per le eccezioni di runtime possiamo usare UncaughtExceptionHandler. UncaughtExceptionHandler è un'interfaccia fornita da Java per gestire le eccezioni in un metodo di esecuzione Thread. Quindi possiamo implementare questa interfaccia e riportare la nostra classe di implementazione sull'oggetto Thread usando il metodo setUncaughtExceptionHandler (). Ma questo gestore deve essere impostato prima di chiamare start () sul battistrada.

se non impostiamo uncaughtExceptionHandler, allora Threads ThreadGroup funge da gestore.

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

Bella spiegazione fornita su http://coder2design.com/thread-creation/#exceptions


0

La mia soluzione con 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

Per coloro che devono interrompere l'esecuzione di tutti i thread e rieseguirli tutti quando uno di essi viene arrestato su un'eccezione:

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

    }

}

Per riassumere :

Hai una funzione principale che crea più thread, ognuno di essi ha UncaughtExceptionHandler che viene attivato da qualsiasi eccezione all'interno di un thread. Aggiungi ogni discussione a un elenco. Se viene attivato un UncaughtExceptionHandler, scorrerà in sequenza attraverso l'elenco, arrestando ogni thread e rilanciando la ricreazione della funzione principale in tutto il thread.


-5

Non puoi farlo, dal momento che non ha davvero senso. Se non lo avessi chiamato, il t.join()thread principale potrebbe trovarsi ovunque nel codice quando il tthread genera un'eccezione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.