Comment intercepter une exception à partir d'un thread

165

J'ai la classe principale Java, dans la classe, je démarre un nouveau thread, dans l'ensemble, il attend que le thread meure. À un moment donné, je lance une exception d'exécution à partir du thread, mais je ne peux pas attraper l'exception lancée à partir du thread dans la classe principale.

Voici le code:

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

    }
  }
}

Quelqu'un sait pourquoi?

NARU
la source

Réponses:

220

Utilisez un 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
la source
13
Que puis-je faire, si je veux lever l'exception à un niveau supérieur?
rodi
6
@rodi save ex dans une variable volatile que le niveau supérieur peut voir dans le gestionnaire (par exemple, variable membre). À l'extérieur, vérifiez si nul, sinon jetez. Ou étendez UEH avec un nouveau champ volatile et stockez l'exception là-bas.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Je veux attraper une exception de l'intérieur de mon fil - sans qu'elle ne soit arrêtée. Cela serait-il utile d'une manière ou d'une autre?
Lealo
42

C'est parce que les exceptions sont locales à un thread et que votre thread principal ne voit pas réellement la runméthode. Je vous suggère de lire plus sur le fonctionnement du threading, mais pour résumer rapidement: votre appel à startdémarrer un thread différent, totalement indépendant de votre thread principal. L'appel à joinattend simplement qu'il soit fait. Une exception qui est levée dans un thread et qui n'est jamais interceptée le termine, c'est pourquoi joinrevient sur votre thread principal, mais l'exception elle-même est perdue.

Si vous voulez être conscient de ces exceptions non interceptées, vous pouvez essayer ceci:

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

Vous trouverez plus d'informations sur la gestion des exceptions non interceptées ici .

abyx
la source
J'aime ça! La définition du gestionnaire avec la méthode statique intercepte Thread.setDefaultUncaughtExceptionHandler()également les exceptions dans le thread "main"
Teo J.
23

Probablement;

  • vous n'avez pas besoin de passer l'exception d'un thread à un autre.
  • si vous voulez gérer une exception, faites-le simplement dans le thread qui l'a lancée.
  • votre thread principal n'a pas besoin d'attendre depuis le thread d'arrière-plan dans cet exemple, ce qui signifie en fait que vous n'avez pas du tout besoin d'un thread d'arrière-plan.

Cependant, supposons que vous ayez besoin de gérer une exception d'un thread enfant à un autre. J'utiliserais un ExecutorService comme celui-ci:

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

impressions

** 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
la source
Mais n'attend pas future.get()ou ne bloque pas jusqu'à ce que l'exécution du thread soit terminée?
Gregor Valentin
@GregorValentin il attend / bloque jusqu'à ce que le thread ait terminé le Runnable / Callable.
Peter Lawrey
3

Utilisez à la Callableplace de Thread, vous pouvez alors appeler Future#get()qui lève toute exception levée par Callable.

artbristol
la source
1
Notez que l'exception levée à l'intérieur Callable.callest encapsulée dans un ExcecutionExceptionet sa cause doit être évaluée.
Karl Richter
3

Actuellement, vous capturez uniquement RuntimeException, une sous-classe de Exception. Mais votre application peut lancer d'autres sous-classes d' Exception . Catch générique Exceptionen plus deRuntimeException

Étant donné que de nombreuses choses ont été modifiées sur le front du thread, utilisez l'API Java avancée.

Préférez avance java.util.concurrent API pour le multi-threading comme ExecutorServiceou ThreadPoolExecutor.

Vous pouvez personnaliser votre ThreadPoolExecutor pour gérer les exceptions.

Exemple de la page de documentation d'Oracle:

Passer outre

protected void afterExecute(Runnable r,
                            Throwable t)

Méthode appelée à la fin de l'exécution du Runnable donné. Cette méthode est appelée par le thread qui a exécuté la tâche. S'il n'est pas nul, le Throwable est l'exception RuntimeException ou Error non interceptée qui a provoqué l'arrêt brutal de l'exécution.

Exemple de code:

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

Usage:

ExtendedExecutor service = new ExtendedExecutor();

J'ai ajouté un constructeur au-dessus du code ci-dessus comme:

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

Vous pouvez modifier ce constructeur en fonction de vos besoins en termes de nombre de threads.

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

J'ai rencontré le même problème ... peu de travail (uniquement pour l'implémentation et non les objets anonymes) ... nous pouvons déclarer l'objet d'exception au niveau de la classe comme nul ... puis l'initialiser dans le bloc catch pour la méthode d'exécution ... s'il y a était une erreur dans la méthode d'exécution, cette variable ne sera pas nulle .. nous pouvons alors avoir une vérification nulle pour cette variable particulière et si ce n'est pas nul, alors il y a eu une exception dans l'exécution du 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;
            }
        }
}     

appeler checkForException () après join ()

Débutant Java
la source
1

Avez-vous joué avec setDefaultUncaughtExceptionHandler () et les méthodes similaires de la classe Thread? Depuis l'API: "En définissant le gestionnaire d'exceptions non interceptées par défaut, une application peut modifier la manière dont les exceptions non interceptées sont gérées (comme la journalisation vers un périphérique ou un fichier spécifique) pour les threads qui accepteraient déjà le comportement" par défaut "du système fourni. "

Vous y trouverez peut-être la réponse à votre problème ... bonne chance! :-)

Dr Snuggles
la source
1

Aussi à partir de Java 8, vous pouvez écrire la réponse de Dan Cruz comme suit:

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
la source
1

AtomicReference est aussi une solution pour passer l'erreur au thread principal. C'est la même approche que celle 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;
    }  

Le seul changement est qu'au lieu de créer une variable volatile, vous pouvez utiliser AtomicReference qui a fait la même chose dans les coulisses.

Uta Alexandru
la source
0

Il est presque toujours faux de prolonger Thread. Je ne peux pas le dire assez clairement.

Règle de multithreading n ° 1: l'extension Threadest incorrecte. *

Si vous implémentez à la Runnableplace, vous verrez votre comportement attendu.

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

    }
  }
}

produit;

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)

* sauf si vous souhaitez modifier la façon dont votre application utilise les threads, ce qui n'est pas le cas dans 99,9% des cas. Si vous pensez être dans les 0,1% des cas, veuillez consulter la règle n ° 1.

Qwerky
la source
7
Cela n'attrape pas l'exception dans la méthode principale.
philwb
L'extension de la classe Thread est fortement déconseillée. J'ai lu ceci et l'explication pourquoi dans la préparation OJPC. livre ... Devinez, ils savent de quoi ils parlent
luigi7up
2
"RuntimeException from main" n'est jamais imprimé ici .. l'exception n'est pas capturée dans main
Amrish Pandey
0

Si vous implémentez Thread.UncaughtExceptionHandler dans la classe qui démarre les threads, vous pouvez définir puis renvoyer l'exception:

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

}

Ce qui provoque la sortie suivante:

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
la source
Pas besoin de rendre Throwable initException volatile, car t.join () se synchronisera.
NickL
0

Gestion des exceptions dans Thread: Par défaut, la méthode run () ne lève aucune exception, donc toutes les exceptions vérifiées à l'intérieur de la méthode run doivent être interceptées et traitées uniquement et pour les exceptions d'exécution, nous pouvons utiliser UncaughtExceptionHandler. UncaughtExceptionHandler est une interface fournie par Java pour gérer les exceptions dans une méthode d'exécution Thread. Nous pouvons donc implémenter cette interface et redéfinir notre classe d'implémentation sur l'objet Thread en utilisant la méthode setUncaughtExceptionHandler (). Mais ce gestionnaire doit être défini avant d'appeler start () sur la bande de roulement.

si nous ne définissons pas uncaughtExceptionHandler, le Threads ThreadGroup agit comme un gestionnaire.

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

Belle explication donnée sur http://coder2design.com/thread-creation/#exceptions

Jatinder Pal
la source
0

Ma solution avec 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
la source
0

Pour ceux qui doivent arrêter tous les threads en cours d'exécution et les réexécuter tous lorsque l'un d'entre eux est arrêté sur une exception:

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

    }

}

Pour résumer :

Vous avez une fonction principale qui crée plusieurs threads, chacun d'eux a UncaughtExceptionHandler qui est déclenché par n'importe quelle exception à l'intérieur d'un thread. Vous ajoutez chaque fil à une liste. Si un UncaughtExceptionHandler est déclenché, il boucle à travers la liste, arrête chaque thread et relance la fonction principale recréant tout le thread.

Antoine Vulcain
la source
-5

Vous ne pouvez pas faire cela, car cela n'a pas vraiment de sens. Si vous n'avez pas appelé, t.join()votre thread principal pourrait se trouver n'importe où dans le code lorsque le tthread lève une exception.

Mathias Schwarz
la source