Cara menangkap Pengecualian dari utas

165

Saya memiliki kelas utama Java, di kelas, saya memulai utas baru, di utas, menunggu sampai utas mati. Pada suatu saat, saya melempar pengecualian runtime dari utas, tetapi saya tidak dapat menangkap pengecualian yang dilemparkan dari utas di kelas utama.

Ini kodenya:

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

    }
  }
}

Adakah yang tahu mengapa?

NARU
sumber

Jawaban:

220

Gunakan 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
sumber
13
Apa yang bisa saya lakukan, jika saya ingin melemparkan pengecualian ke level yang lebih tinggi?
rodi
6
@rodi menyimpan ex ke variabel volatil yang dapat dilihat oleh level atas di handler (mis. variabel anggota). Di luar, periksa apakah nol, jika tidak lempar. Atau perpanjang UEH dengan bidang volatil baru dan simpan pengecualian di sana.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Saya ingin menangkap pengecualian dari dalam utas saya - tanpa dihentikan. Apakah ini entah bagaimana berguna?
Lealo
42

Itu karena pengecualian bersifat lokal untuk utas, dan utas utama Anda sebenarnya tidak melihat runmetodenya. Saya sarankan Anda membaca lebih lanjut tentang cara kerja threading, tetapi untuk meringkas dengan cepat: panggilan Anda untuk startmemulai utas yang berbeda, sama sekali tidak terkait dengan utas utama Anda. Panggilan untuk joinhanya menunggu untuk dilakukan. Pengecualian yang dilemparkan ke utas dan tidak pernah tertangkap akan menghentikannya, itulah sebabnya joinkembali ke utas utama Anda, tetapi pengecualian itu sendiri hilang.

Jika Anda ingin mengetahui pengecualian yang tidak tertangkap ini, Anda dapat mencoba ini:

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

Informasi lebih lanjut tentang penanganan pengecualian tanpa tertangkap dapat ditemukan di sini .

abyx
sumber
Aku suka itu! Mengatur pawang dengan metode statis Thread.setDefaultUncaughtExceptionHandler()juga menangkap pengecualian di utas "utama"
Teo J.
23

Yang paling disukai;

  • Anda tidak perlu meneruskan pengecualian dari satu utas ke utas lainnya.
  • jika Anda ingin menangani pengecualian, lakukan saja di utas yang melemparkannya.
  • utas utama Anda tidak perlu menunggu dari utas latar belakang dalam contoh ini, yang sebenarnya berarti Anda tidak memerlukan utas latar belakang sama sekali.

Namun, anggaplah Anda memang perlu menangani pengecualian dari utas anak lainnya. Saya akan menggunakan ExecutorService seperti ini:

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

cetakan

** 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
sumber
Tetapi tidak future.get()menunggu atau memblokir sampai utas telah selesai dieksekusi?
Gregor Valentin
@GregorValentin menunggu / memblokir sampai utas telah menyelesaikan Runnable / Callable.
Peter Lawrey
3

Gunakan Callablesebagai ganti Thread, maka Anda bisa menelepon Future#get()yang melempar pengecualian yang dilontarkan Callable.

artbristol
sumber
1
Perhatikan bahwa pengecualian yang dimasukkan ke dalam Callable.calldibungkus dengan ExcecutionExceptiondan penyebabnya harus dievaluasi.
Karl Richter
3

Saat ini Anda hanya menangkap RuntimeException, sub kelas dari Exception. Tetapi aplikasi Anda dapat membuang sub-kelas Pengecualian lainnya . ExceptionSelain generikRuntimeException

Karena banyak hal telah diubah di bagian depan Threading, gunakan API java lanjutan.

Lebih memilih muka java.util.concurrent API untuk multi-threading seperti ExecutorServiceatau ThreadPoolExecutor.

Anda dapat menyesuaikan ThreadPoolExecutor Anda untuk menangani pengecualian.

Contoh dari halaman dokumentasi oracle:

Mengesampingkan

protected void afterExecute(Runnable r,
                            Throwable t)

Metode dipanggil setelah selesainya eksekusi Runnable yang diberikan. Metode ini dipanggil oleh utas yang menjalankan tugas. Jika bukan nol, Throwable adalah RuntimeException atau Error yang tidak tertangkap yang menyebabkan eksekusi berakhir tiba-tiba.

Kode contoh:

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

Pemakaian:

ExtendedExecutor service = new ExtendedExecutor();

Saya telah menambahkan satu konstruktor di atas kode di atas sebagai:

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

Anda dapat mengubah konstruktor ini agar sesuai dengan kebutuhan Anda pada jumlah utas.

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

Saya menghadapi masalah yang sama ... sedikit kerja di sekitar (hanya untuk implementasi bukan objek anonim) ... kita dapat mendeklarasikan objek pengecualian tingkat kelas sebagai null ... lalu inisialisasi di dalam blok tangkap untuk menjalankan metode ... jika ada adalah kesalahan dalam menjalankan metode, variabel ini tidak akan menjadi nol .. kita kemudian dapat memiliki pemeriksaan nol untuk variabel tertentu ini dan jika tidak nol maka ada pengecualian di dalam eksekusi 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;
            }
        }
}     

panggil checkForException () setelah bergabung ()

Java Beginner
sumber
1

Apakah Anda bermain-main dengan setDefaultUncaughtExceptionHandler () dan metode serupa dari kelas Thread? Dari API: "Dengan menetapkan penangan pengecualian tanpa tertangkap default, aplikasi dapat mengubah cara penanganan pengecualian tanpa penangkapan (seperti masuk ke perangkat tertentu, atau file) untuk utas yang sudah menerima perilaku" default "apa pun yang sistem disediakan. "

Anda mungkin menemukan jawaban untuk masalah Anda di sana ... semoga berhasil! :-)

Dr Snuggles
sumber
1

Juga dari Java 8 Anda dapat menulis jawaban Dan Cruz sebagai:

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

AtomicReference juga merupakan solusi untuk meneruskan kesalahan ke utas utama. Pendekatan yang sama seperti yang dilakukan 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;
    }  

Satu-satunya perubahan adalah bahwa alih-alih membuat variabel volatil Anda dapat menggunakan AtomicReference yang melakukan hal yang sama di belakang layar.

Uta Alexandru
sumber
0

Hampir selalu salah untuk memperpanjang Thread. Saya tidak bisa menyatakan ini dengan cukup kuat.

Aturan Multithreading # 1: Memperluas Threaditu salah. *

Jika Anda menerapkannya, RunnableAnda akan melihat perilaku yang Anda harapkan.

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

    }
  }
}

menghasilkan;

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)

* kecuali jika Anda ingin mengubah cara aplikasi Anda menggunakan utas, yang dalam 99,9% kasus Anda tidak. Jika menurut Anda Anda berada dalam 0,1% kasus, lihat aturan # 1.

Qwerky
sumber
7
Ini tidak menangkap pengecualian dalam metode utama.
philwb
Memperluas kelas Thread sangat tidak disarankan. Saya membaca ini dan penjelasannya mengapa dalam persiapan OJPC. buku ... Tebak, mereka tahu apa yang mereka bicarakan
luigi7up
2
"RuntimeException from main" tidak pernah dicetak di sini .. pengecualian tidak tertangkap di main
Amrish Pandey
0

Jika Anda menerapkan Thread.UncaughtExceptionHandler di kelas yang memulai Threads, Anda bisa mengatur dan kemudian menyusun kembali pengecualian:

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

}

Yang menyebabkan output berikut:

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
sumber
Tidak perlu menjadikan Thitable initException volatile, karena t.join () akan menyinkronkan.
NickL
0

Penanganan pengecualian di Thread: Secara default metode run () tidak membuang pengecualian, jadi semua pengecualian yang diperiksa di dalam metode run harus ditangkap dan ditangani di sana saja dan untuk pengecualian runtime kita bisa menggunakan UncaughtExceptionHandler. UncaughtExceptionHandler adalah antarmuka yang disediakan oleh Java untuk menangani pengecualian dalam metode Thread run. Jadi kita bisa mengimplementasikan antarmuka ini dan mengatur kembali kelas implementasi kita ke objek Thread menggunakan metode setUncaughtExceptionHandler (). Tetapi pawang ini harus diatur sebelum kita memanggil start () pada tapak.

jika kita tidak menyetel uncaughtExceptionHandler maka Threads ThreadGroup bertindak sebagai penangan.

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

Penjelasan bagus diberikan di http://coder2design.com/thread-creation/#exceptions

Jatinder Pal
sumber
0

Solusi saya dengan 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
sumber
0

Bagi mereka yang perlu menghentikan semua Threads menjalankan dan menjalankan kembali semuanya ketika salah satu dari mereka dihentikan pada Pengecualian:

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

    }

}

Untuk menyimpulkan :

Anda memiliki fungsi utama yang membuat banyak utas, masing-masing memiliki UncaughtExceptionHandler yang dipicu oleh setiap pengecualian dalam utas. Anda menambahkan setiap Thread ke Daftar. Jika UncaughtExceptionHandler dipicu itu akan berulang melalui Daftar, hentikan setiap Thread dan meluncurkan kembali fungsi utama rekreasi semua Thread.

Antoine Vulcain
sumber
-5

Anda tidak dapat melakukan ini, karena itu tidak masuk akal. Jika Anda belum menelepon t.join()maka utas utama Anda bisa berada di mana saja dalam kode ketika tutas membuat pengecualian.

Mathias Schwarz
sumber