Como capturar uma exceção de um thread

165

Eu tenho a classe principal Java, na classe, inicio um novo thread, no principal, ele aguarda até que o thread morra. Em algum momento, lanço uma exceção de tempo de execução do encadeamento, mas não consigo capturar a exceção lançada no encadeamento na classe principal.

Aqui está o 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)
    {

    }
  }
}

Alguém sabe por que?

NARU
fonte

Respostas:

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
fonte
13
O que posso fazer se quiser lançar a exceção para um nível superior?
Rod25
6
@rodi salva ex em uma variável volátil que o nível superior pode ver no manipulador (por exemplo, variável de membro). Lá fora, verifique se nulo, caso contrário, jogue. Ou estenda o UEH com um novo campo volátil e armazene a exceção lá.
Ciro Santilli # 19/15
1
Quero capturar uma exceção de dentro do meu segmento - sem que seja interrompido. De alguma forma isso seria útil?
Lealo 11/08/19
42

Isso ocorre porque as exceções são locais em um encadeamento e o encadeamento principal não vê o runmétodo. Sugiro que você leia mais sobre como o encadeamento funciona, mas para resumir rapidamente: sua chamada para startiniciar um encadeamento diferente, totalmente não relacionado ao seu encadeamento principal. A chamada joinsimplesmente espera que isso seja feito. Uma exceção lançada em um encadeamento e nunca capturada o encerra, e é por isso que joinretorna ao seu encadeamento principal, mas a exceção em si é perdida.

Se você deseja conhecer essas exceções não detectadas, tente o seguinte:

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

Mais informações sobre o tratamento de exceções não capturadas podem ser encontradas aqui .

abyx
fonte
Eu gosto disso! Definir o manipulador com o método estático Thread.setDefaultUncaughtExceptionHandler()também captura exceções no thread "main"
Teo J.
23

Provavelmente;

  • você não precisa passar a exceção de um thread para outro.
  • se você quiser lidar com uma exceção, basta fazê-lo no segmento que a lançou.
  • seu encadeamento principal não precisa esperar do encadeamento em segundo plano neste exemplo, o que realmente significa que você não precisa de um encadeamento em segundo plano.

No entanto, vamos supor que você precise manipular uma exceção de um thread filho outro. Eu usaria um 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");

impressões

** 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
fonte
Mas não future.get()espera ou bloqueia até que o thread termine a execução?
22417 Gregor Valentin
@GregorValentin espera / bloqueia até que o thread termine o Runnable / Callable.
22417 Peter Lawrey
3

Use em Callablevez de Thread, então você pode chamar o Future#get()que lança qualquer exceção lançada pelo Callable.

artbristol
fonte
1
Observe que a exceção lançada dentro Callable.callé agrupada em ExcecutionExceptione sua causa deve ser avaliada.
Karl Richter
3

No momento, você está capturando apenas RuntimeExceptionuma subclasse de Exception. Mas seu aplicativo pode lançar outras subclasses de exceção . Capturar genérico Exception, além deRuntimeException

Como muitas coisas foram alteradas na frente do Threading, use a API java avançada.

Prefira a API avançada java.util.concurrent para multi-threading como ExecutorServiceou ThreadPoolExecutor.

Você pode personalizar seu ThreadPoolExecutor para manipular exceções.

Exemplo da página de documentação do oracle:

Sobrepor

protected void afterExecute(Runnable r,
                            Throwable t)

Método invocado após a conclusão da execução do Runnable fornecido. Este método é chamado pelo encadeamento que executou a tarefa. Se não for nulo, o Throwable é o RuntimeException ou Error não capturado que causou o término abrupto da execução.

Código de exemplo:

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

Eu adicionei um construtor sobre o código acima como:

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

Você pode alterar esse construtor para atender aos seus requisitos no número de threads.

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

Eu enfrentei o mesmo problema ... pouco trabalho (apenas para implementação não objetos anônimos) ... podemos declarar o objeto de exceção no nível de classe como nulo ... e inicializá-lo dentro do bloco catch para o método run ... se houver Foi um erro no método run, esta variável não será nula. Podemos fazer uma verificação nula para essa variável em particular e, se não for nula, houve uma exceção na execução do encadeamento.

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

chame checkForException () após join ()

Iniciante em Java
fonte
1

Você brincou com os métodos setDefaultUncaughtExceptionHandler () e similares da classe Thread? Na API: "Ao definir o manipulador de exceção não capturado padrão, um aplicativo pode alterar a maneira como as exceções não capturadas são tratadas (como registrar em um dispositivo ou arquivo específico) para os segmentos que já aceitariam qualquer comportamento" padrão ". sistema fornecido. "

Você pode encontrar a resposta para o seu problema lá ... boa sorte! :-)

Dr. Snuggles
fonte
1

Também do Java 8, você pode escrever a resposta 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
fonte
1

AtomicReference também é uma solução para passar o erro ao thread principal. É a mesma abordagem que a 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;
    }  

A única mudança é que, em vez de criar uma variável volátil, você pode usar o AtomicReference, que fez a mesma coisa nos bastidores.

Uta Alexandru
fonte
0

É quase sempre errado estender Thread. Não posso afirmar isso com força suficiente.

Regra multithreading # 1: a extensão Threadestá incorreta. *

Se você implementar Runnable, verá o seu comportamento 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) {

    }
  }
}

produz;

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 você queira alterar a maneira como seu aplicativo usa threads, o que em 99,9% dos casos você não usa. Se você pensa que está em 0,1% dos casos, consulte a regra nº 1.

Qwerky
fonte
7
Isso não captura a exceção no método principal.
philwb
Estender a classe Thread é altamente desencorajado. Eu li isso e a explicação por que na preparação do OJPC. livro ... Guess, eles sabem o que estão falando
luigi7up
2
"RuntimeException da principal" nunca é impressa aqui .. exceção não é capturada no principal
Amrish Pandey
0

Se você implementar Thread.UncaughtExceptionHandler na classe que inicia os Threads, poderá definir e, em seguida, repetir novamente a exceção:

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

}

O que causa a seguinte saída:

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
fonte
Não há necessidade de tornar Throwable initException volátil, pois t.join () será sincronizado.
Nickl
0

Manipulação de exceções no Thread: Por padrão, o método run () não lança nenhuma exceção; portanto, todas as exceções verificadas dentro do método run devem ser capturadas e manipuladas apenas nele e, para exceções de tempo de execução, podemos usar UncaughtExceptionHandler. UncaughtExceptionHandler é uma interface fornecida pelo Java para manipular exceções em um método de execução de Thread. Portanto, podemos implementar essa interface e retornar nossa classe de implementação ao objeto Thread usando o método setUncaughtExceptionHandler (). Mas esse manipulador deve ser definido antes de chamarmos start () na banda de rodagem.

se não definirmos uncaughtExceptionHandler, o ThreadGroup Threads atuará como um manipulador.

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

Boa explicação dada em http://coder2design.com/thread-creation/#exceptions

Jatinder Pal
fonte
0

Minha solução com o 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
fonte
0

Para aqueles que precisam parar todos os Threads em execução e executar novamente todos eles quando qualquer um deles estiver parado em uma exceção:

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

    }

}

Resumindo :

Você tem uma função principal que cria vários encadeamentos, cada um deles com UncaughtExceptionHandler, que é acionado por qualquer exceção dentro de um encadeamento. Você adiciona todos os threads a uma lista. Se um UncaughtExceptionHandler for acionado, ele percorrerá a Lista, parará todos os Threads e reiniciará a função principal recreação de todo o Thread.

Antoine Vulcain
fonte
-5

Você não pode fazer isso, pois isso realmente não faz sentido. Se você não tivesse chamado t.join(), o encadeamento principal poderia estar em qualquer lugar no código quando o tencadeamento lança uma exceção.

Mathias Schwarz
fonte