So fangen Sie eine Ausnahme von einem Thread ab

165

Ich habe Java-Hauptklasse, in der Klasse starte ich einen neuen Thread, in der Hauptklasse wartet er, bis der Thread stirbt. Irgendwann löse ich eine Laufzeitausnahme aus dem Thread, aber ich kann die vom Thread in der Hauptklasse ausgelöste Ausnahme nicht abfangen.

Hier ist der 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)
    {

    }
  }
}

Weiß jemand warum?

NARU
quelle

Antworten:

220

Verwenden Sie 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
quelle
13
Was kann ich tun, wenn ich die Ausnahme auf eine höhere Ebene werfen möchte?
Rodi
6
@rodi speichere ex in eine flüchtige Variable, die die obere Ebene im Handler sehen kann (z. B. Mitgliedsvariable). Überprüfen Sie draußen, ob null, andernfalls werfen Sie. Oder erweitern Sie UEH um ein neues flüchtiges Feld und speichern Sie die Ausnahme dort.
Ciro Santilli 法轮功 冠状 病 六四 事件 19
1
Ich möchte eine Ausnahme aus meinem Thread heraus abfangen - ohne dass sie gestoppt wird. Wäre das irgendwie von Nutzen?
Lealo
42

Dies liegt daran, dass Ausnahmen für einen Thread lokal sind und Ihr Hauptthread die runMethode nicht sieht . Ich schlage vor, Sie lesen mehr über die Funktionsweise von Threading, aber um es kurz zusammenzufassen: Ihr Aufruf, starteinen anderen Thread zu starten, der völlig unabhängig von Ihrem Haupt-Thread ist. Der Anruf joinwartet einfach darauf, dass er erledigt wird. Eine Ausnahme, die in einem Thread ausgelöst und nie abgefangen wird, beendet ihn, weshalb der joinHauptthread zurückgegeben wird, die Ausnahme selbst jedoch verloren geht.

Wenn Sie sich dieser nicht erfassten Ausnahmen bewusst sein möchten, können Sie Folgendes versuchen:

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

Weitere Informationen zur Behandlung nicht erfasster Ausnahmen finden Sie hier .

Abyx
quelle
Ich mag es! Das Setzen des Handlers mit der statischen Methode Thread.setDefaultUncaughtExceptionHandler()fängt auch Ausnahmen im Thread "main" ab
Teo J.
23

Höchstwahrscheinlich;

  • Sie müssen die Ausnahme nicht von einem Thread an einen anderen übergeben.
  • Wenn Sie eine Ausnahme behandeln möchten, tun Sie dies einfach in dem Thread, der sie ausgelöst hat.
  • Ihr Hauptthread muss in diesem Beispiel nicht vom Hintergrundthread warten, was bedeutet, dass Sie überhaupt keinen Hintergrundthread benötigen.

Nehmen wir jedoch an, Sie müssen eine Ausnahme von einem untergeordneten Thread eines anderen behandeln. Ich würde einen ExecutorService wie folgt verwenden:

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

druckt

** 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
quelle
Aber nicht future.get()warten oder blockieren, bis der Thread die Ausführung beendet hat?
Gregor Valentin
@ GregorValentin es wartet / blockiert, bis der Thread Runnable / Callable beendet hat.
Peter Lawrey
3

Verwenden Sie Callableanstelle von Thread, dann können Sie aufrufen, Future#get()was jede Ausnahme auslöst, die der Callable ausgelöst hat.

Artbristol
quelle
1
Beachten Sie, dass die darin ausgelöste Ausnahme Callable.callin eine eingeschlossen ist ExcecutionExceptionund ihre Ursache bewertet werden muss.
Karl Richter
3

Derzeit fangen Sie nur RuntimeExceptioneine Unterklasse von Exception. Ihre Anwendung kann jedoch andere Unterklassen von Ausnahmen auslösen . Catch Generika Exceptionzusätzlich zuRuntimeException

Verwenden Sie die erweiterte Java-API, da viele Dinge im Threading-Bereich geändert wurden.

Bevorzugen Sie die erweiterte java.util.concurrent- API für Multithreading wie ExecutorServiceoder ThreadPoolExecutor.

Sie können Ihren ThreadPoolExecutor so anpassen , dass Ausnahmen behandelt werden.

Beispiel von der Oracle-Dokumentationsseite:

Überschreiben

protected void afterExecute(Runnable r,
                            Throwable t)

Methode, die nach Abschluss der Ausführung des angegebenen Runnable aufgerufen wird. Diese Methode wird von dem Thread aufgerufen, der die Aufgabe ausgeführt hat. Wenn nicht null, ist Throwable die nicht erfasste RuntimeException oder der Fehler, die dazu geführt haben, dass die Ausführung abrupt beendet wurde.

Beispielcode:

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

Verwendung:

ExtendedExecutor service = new ExtendedExecutor();

Ich habe einen Konstruktor über dem obigen Code hinzugefügt als:

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

Sie können diesen Konstruktor an Ihre Anforderungen an die Anzahl der Threads anpassen.

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

Ich hatte das gleiche Problem ... wenig Arbeit (nur für die Implementierung, keine anonymen Objekte) ... wir können das Ausnahmeobjekt auf Klassenebene als null deklarieren ... und es dann im catch-Block für die Ausführungsmethode initialisieren ... falls vorhanden war ein Fehler in der Ausführungsmethode, diese Variable wird nicht null sein. Wir können dann eine Nullprüfung für diese bestimmte Variable durchführen lassen. Wenn sie nicht null ist, gab es eine Ausnahme innerhalb der Thread-Ausführung.

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

Rufen Sie checkForException () nach join () auf.

Java Anfänger
quelle
1

Haben Sie mit setDefaultUncaughtExceptionHandler () und den gleichen Methoden der Thread-Klasse herumgespielt? Über die API: "Durch Festlegen des Standard-Handlers für nicht erfasste Ausnahmen kann eine Anwendung die Art und Weise ändern, in der nicht erfasste Ausnahmen behandelt werden (z. B. die Protokollierung auf einem bestimmten Gerät oder einer bestimmten Datei), für diejenigen Threads, die bereits das" Standard "-Verhalten akzeptieren System zur Verfügung gestellt. "

Vielleicht finden Sie dort die Antwort auf Ihr Problem ... viel Glück! :-)

Dr. Snuggles
quelle
1

Auch aus Java 8 können Sie Dan Cruz Antwort schreiben als:

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

AtomicReference ist auch eine Lösung, um den Fehler an den Hauptthread zu übergeben. Dies ist der gleiche Ansatz wie der von 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;
    }  

Die einzige Änderung besteht darin, dass Sie anstelle einer flüchtigen Variablen AtomicReference verwenden können, die hinter den Kulissen dasselbe getan hat.

Uta Alexandru
quelle
0

Es ist fast immer falsch zu verlängern Thread. Ich kann das nicht stark genug sagen.

Multithreading-Regel Nr. 1: Das Erweitern Threadist falsch. *

Wenn Sie Runnablestattdessen implementieren , sehen Sie Ihr erwartetes Verhalten.

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

    }
  }
}

produziert;

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)

* es sei denn, Sie möchten die Art und Weise ändern, in der Ihre Anwendung Threads verwendet, was in 99,9% der Fälle nicht der Fall ist. Wenn Sie glauben, dass Sie sich in 0,1% der Fälle befinden, lesen Sie bitte Regel 1.

Qwerky
quelle
7
Dies fängt die Ausnahme in der Hauptmethode nicht ab.
Philwb
Von der Erweiterung der Thread-Klasse wird dringend abgeraten. Ich habe dies und die Erklärung gelesen, warum in der OJPC-Vorbereitung. Buch ...
Vermutlich
2
"RuntimeException from main" wird hier nie gedruckt. Ausnahme wird nicht in main abgefangen
Amrish Pandey
0

Wenn Sie Thread.UncaughtExceptionHandler in einer Klasse implementieren, die die Threads startet, können Sie die Ausnahme festlegen und anschließend erneut auslösen:

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

}

Was die folgende Ausgabe verursacht:

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
quelle
Throwable initException muss nicht flüchtig gemacht werden, da t.join () synchronisiert wird.
NickL
0

Ausnahmebehandlung in Thread: Standardmäßig löst die run () -Methode keine Ausnahme aus, daher müssen alle überprüften Ausnahmen innerhalb der run-Methode nur dort abgefangen und behandelt werden. Für Laufzeitausnahmen können wir UncaughtExceptionHandler verwenden. UncaughtExceptionHandler ist eine von Java bereitgestellte Schnittstelle zur Behandlung von Ausnahmen in einer Thread-Ausführungsmethode. So können wir diese Schnittstelle implementieren und unsere Implementierungsklasse mithilfe der setUncaughtExceptionHandler () -Methode auf das Thread-Objekt zurücksetzen. Dieser Handler muss jedoch gesetzt werden, bevor wir start () auf dem Profil aufrufen.

Wenn wir uncaughtExceptionHandler nicht festlegen, fungiert die Threads ThreadGroup als Handler.

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

Schöne Erklärung unter http://coder2design.com/thread-creation/#exceptions

Jatinder Pal
quelle
0

Meine Lösung mit 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
quelle
0

Für diejenigen, die alle Threads stoppen und erneut ausführen müssen, wenn einer von ihnen aufgrund einer Ausnahme gestoppt wird:

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

    }

}

Um zusammenzufassen :

Sie haben eine Hauptfunktion, die mehrere Threads erstellt. Jeder von ihnen verfügt über UncaughtExceptionHandler, der durch eine Ausnahme innerhalb eines Threads ausgelöst wird. Sie fügen jeden Thread einer Liste hinzu. Wenn ein UncaughtExceptionHandler ausgelöst wird, durchläuft er die Liste, stoppt jeden Thread und startet die Neuerstellung der Hauptfunktion für den gesamten Thread neu.

Antoine Vulcain
quelle
-5

Sie können dies nicht tun, da es nicht wirklich Sinn macht. Wenn Sie nicht aufgerufen haben, kann sich t.join()Ihr Hauptthread an einer beliebigen Stelle im Code befinden, wenn der tThread eine Ausnahme auslöst.

Mathias Schwarz
quelle