Cómo atrapar una excepción de un hilo

165

Tengo la clase principal de Java, en la clase, comienzo un nuevo hilo, en el principal, espera hasta que el hilo muere. En algún momento, lanzo una excepción de tiempo de ejecución desde el hilo, pero no puedo atrapar la excepción lanzada desde el hilo en la clase principal.

Aquí está el código:

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

    }
  }
}

¿Alguien sabe por qué?

NARU
fuente

Respuestas:

220

Use 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();
Dan Cruz
fuente
13
¿Qué puedo hacer si quiero lanzar la excepción a un nivel superior?
rodi
66
@rodi save ex en una variable volátil que el nivel superior puede ver en el controlador (por ejemplo, la variable miembro). En el exterior, verifique si es nulo, de lo contrario arroje. O extienda UEH con un nuevo campo volátil y almacene la excepción allí.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Quiero detectar una excepción desde el interior de mi hilo, sin que se detenga. ¿Sería esto de alguna manera útil?
Lealo
42

Esto se debe a que las excepciones son locales a un hilo, y su hilo principal en realidad no ve el runmétodo. Le sugiero que lea más sobre cómo funcionan los subprocesos, pero para resumir rápidamente: su llamado a startiniciar un hilo diferente, totalmente ajeno a su hilo principal. La llamada a joinsimplemente espera a que se haga. Una excepción que se lanza en un subproceso y nunca se captura lo termina, por lo que joinregresa a su subproceso principal, pero la excepción en sí se pierde.

Si desea conocer estas excepciones no detectadas, puede intentar esto:

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

Puede encontrar más información sobre el manejo de excepciones no detectadas aquí .

abyx
fuente
¡Me gusta eso! Al configurar el controlador con el método estático, Thread.setDefaultUncaughtExceptionHandler()también se detectan excepciones en el hilo "principal"
Teo J.
23

Más probable;

  • no necesita pasar la excepción de un hilo a otro.
  • si desea manejar una excepción, simplemente hágalo en el hilo que la arrojó.
  • su hilo principal no necesita esperar del hilo de fondo en este ejemplo, lo que en realidad significa que no necesita un hilo de fondo en absoluto.

Sin embargo, supongamos que necesita manejar una excepción de un subproceso hijo a otro. Usaría un ExecutorService como este:

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

huellas dactilares

** 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
Peter Lawrey
fuente
¿Pero no future.get()espera o bloquea hasta que el hilo ha finalizado la ejecución?
Gregor Valentin
@GregorValentin espera / bloquea hasta que el hilo haya finalizado el Runnable / Callable.
Peter Lawrey
3

Use en Callablelugar de Thread, luego puede llamar, lo Future#get()que arroja cualquier excepción que lanzó el Llamable.

artbristol
fuente
1
Tenga en cuenta que la excepción lanzada dentro Callable.callestá envuelta en una ExcecutionExceptiony su causa tiene que ser evaluada.
Karl Richter
3

Actualmente solo está capturando RuntimeException, una subclase de Exception. Pero su aplicación puede arrojar otras subclases de Excepción . Captura genérica Exceptionademás deRuntimeException

Dado que muchas cosas se han cambiado en Threading front, use la API avanzada de Java.

Prefiere la API avanzada java.util.concurrent para subprocesos múltiples como ExecutorServiceo ThreadPoolExecutor.

Puede personalizar su ThreadPoolExecutor para manejar excepciones.

Ejemplo de la página de documentación de Oracle:

Anular

protected void afterExecute(Runnable r,
                            Throwable t)

Método invocado al finalizar la ejecución del Runnable dado. Este método es invocado por el hilo que ejecutó la tarea. Si no es nulo, Throwable es la excepción o error de ejecución no detectado que provocó que la ejecución finalizara abruptamente.

Código de ejemplo:

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

He agregado un constructor encima del código anterior como:

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

Puede cambiar este constructor para que se adapte a sus requisitos en cuanto al número de subprocesos.

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

Me enfrenté al mismo problema ... poca solución (solo para la implementación de objetos no anónimos) ... podemos declarar el objeto de excepción de nivel de clase como nulo ... luego inicializarlo dentro del bloque catch para el método de ejecución ... si hay fue un error en el método de ejecución, esta variable no será nula ... entonces podemos hacer una verificación nula para esta variable en particular y si no es nula, entonces hubo una excepción dentro de la ejecución del hilo.

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

llame a checkForException () después de unirse ()

Principiante de Java
fuente
1

¿Jugaste con setDefaultUncaughtExceptionHandler () y los métodos similares de la clase Thread? Desde la API: "Al configurar el controlador de excepción no capturado predeterminado, una aplicación puede cambiar la forma en que se manejan las excepciones no capturadas (como iniciar sesión en un dispositivo o archivo específico) para aquellos subprocesos que ya aceptarían cualquier comportamiento" predeterminado " sistema proporcionado ".

Puede encontrar la respuesta a su problema allí ... ¡buena suerte! :-)

Dr. Snuggles
fuente
1

También desde Java 8 puede escribir la respuesta de Dan Cruz como:

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
fuente
1

AtomicReference también es una solución para pasar el error al hilo principal. Es el mismo enfoque que el de 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;
    }  

El único cambio es que, en lugar de crear una variable volátil, puede usar AtomicReference, que hizo lo mismo detrás de escena.

Uta Alexandru
fuente
0

Casi siempre es incorrecto extender Thread. No puedo decir esto con suficiente fuerza.

Regla de subprocesos múltiples # 1: Ampliar Threades incorrecto. *

Si implementa Runnableen su lugar, verá su comportamiento esperado.

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 menos que desee cambiar la forma en que su aplicación usa subprocesos, que en el 99.9% de los casos no lo hace. Si cree que está en el 0.1% de los casos, consulte la regla n. ° 1.

Qwerky
fuente
77
Esto no atrapa la excepción en el método principal.
philwb 01 de
No se recomienda extender la clase Thread. Leí esto y la explicación de por qué en la preparación de OJPC. libro ... Supongo, saben de lo que están hablando
luigi7up
2
"RuntimeException from main" nunca se imprime aquí ... la excepción no se detecta en main
Amrish Pandey
0

Si implementa Thread.UncaughtExceptionHandler en la clase que inicia los subprocesos, puede establecer y volver a lanzar la excepción:

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

}

Lo que provoca el siguiente resultado:

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)
Stefano
fuente
No es necesario hacer que Throwable initException sea volátil, ya que t.join () se sincronizará.
NickL
0

Manejo de excepciones en Thread: por defecto, el método run () no arroja ninguna excepción, por lo que todas las excepciones marcadas dentro del método run deben capturarse y manejarse allí solo y para las excepciones de tiempo de ejecución podemos usar UncaughtExceptionHandler. UncaughtExceptionHandler es una interfaz proporcionada por Java para manejar excepciones en un método de ejecución Thread. Por lo tanto, podemos implementar esta interfaz y restablecer nuestra clase de implementación al objeto Thread utilizando el método setUncaughtExceptionHandler (). Pero este controlador debe configurarse antes de que llamemos a start () en la banda de rodadura.

si no establecemos uncaughtExceptionHandler, Threads ThreadGroup actúa como un controlador.

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

Buena explicación en http://coder2design.com/thread-creation/#exceptions

Jatinder Pal
fuente
0

Mi solución 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.");
}
Zinan Xing
fuente
0

Para aquellos que necesitan detener la ejecución de todos los subprocesos y volver a ejecutarlos cuando alguno de ellos se detiene en una excepción:

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

    }

}

Para resumir :

Tiene una función principal que crea múltiples subprocesos, cada uno de ellos tiene UncaughtExceptionHandler que se activa por cualquier excepción dentro de un subproceso. Agrega todos los hilos a una lista. Si se desencadena un UncaughtExceptionHandler, recorrerá la lista, detendrá cada subproceso y reiniciará la función principal que recrea todo el subproceso.

Antoine Vulcain
fuente
-5

No puedes hacer esto, ya que realmente no tiene sentido. Si no hubiera llamado, t.join()entonces su hilo principal podría estar en cualquier parte del código cuando el thilo arroje una excepción.

Mathias Schwarz
fuente