วิธีการจับข้อยกเว้นจากเธรด


165

ฉันมีคลาส Java หลักในชั้นเรียนฉันเริ่มหัวข้อใหม่ในหลักมันจะรอจนกว่าด้ายจะตาย ในบางช่วงเวลาฉันโยนข้อยกเว้นรันไทม์จากเธรด แต่ฉันไม่สามารถจับข้อยกเว้นที่โยนออกจากเธรดในคลาสหลักได้

นี่คือรหัส:

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

    }
  }
}

ใครรู้ว่าทำไม

คำตอบ:


220

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

13
ฉันต้องทำอย่างไรถ้าฉันต้องการส่งข้อยกเว้นไปยังระดับที่สูงกว่า
Rodi

6
@rodi บันทึก ex เป็นตัวแปรระเหยที่ระดับบนสามารถเห็นได้ในตัวจัดการ (เช่นตัวแปรสมาชิก) นอกตรวจสอบว่าเป็นโมฆะหรือไม่ หรือขยาย UEH ด้วยสนามระเหยใหม่และเก็บข้อยกเว้นไว้ที่นั่น
Ciro Santilli 郝海东冠状病六四事件法轮功

1
ฉันต้องการที่จะรับข้อยกเว้นจากภายในเธรดของฉัน - โดยไม่หยุด จะใช้อย่างนี้หรือไม่?
Lealo

42

นั่นเป็นเพราะข้อยกเว้นเป็นแบบโลคัลสำหรับเธรดและเธรดหลักของคุณจะไม่เห็นrunเมธอด ฉันขอแนะนำให้คุณอ่านเพิ่มเติมเกี่ยวกับวิธีการทำงานของเธรด แต่เพื่อสรุปอย่างรวดเร็ว: การโทรของคุณเพื่อstartเริ่มเธรดอื่นไม่เกี่ยวข้องกับเธรดหลักของคุณทั้งหมด การเรียกให้joinรอเพียงเพื่อให้เสร็จสิ้น ข้อยกเว้นที่ถูกโยนลงในเธรดและไม่เคยถูกจับได้ยกเลิกซึ่งเป็นสาเหตุที่joinส่งคืนบนเธรดหลักของคุณ แต่ข้อยกเว้นจะหายไปเอง

หากคุณต้องการทราบข้อยกเว้นที่ไม่ถูกตรวจสอบเหล่านี้คุณสามารถลอง:

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

ข้อมูลเพิ่มเติมเกี่ยวกับการจัดการข้อยกเว้น uncaught สามารถพบได้ที่นี่


ฉันชอบมัน! การตั้งค่าตัวจัดการด้วยวิธีสแตติกThread.setDefaultUncaughtExceptionHandler()ยังจับข้อยกเว้นในเธรด "main"
Teo J.

38

สิ่งนี้จะอธิบายการเปลี่ยนสถานะของเธรดที่ขึ้นอยู่กับว่ามีข้อยกเว้นเกิดขึ้นหรือไม่:

การจัดการเธรดและข้อยกเว้น

ที่มา: http://www-public.imtbs-tsp.eu/~gibson/Teaching/CSC7322/L8-ExceptionsAndThreads.pdf


12
คุณสร้างไดอะแกรมหรือไม่ ถ้าไม่มันคืออะไร
flow2k

23

มีโอกาสมากที่สุด;

  • คุณไม่จำเป็นต้องส่งข้อยกเว้นจากเธรดหนึ่งไปยังเธรดอื่น
  • ถ้าคุณต้องการที่จะจัดการกับข้อยกเว้นเพียงแค่ทำมันในหัวข้อที่โยนมัน
  • เธรดหลักของคุณไม่จำเป็นต้องรอจากเธรดพื้นหลังในตัวอย่างนี้ซึ่งจริงๆแล้วหมายความว่าคุณไม่จำเป็นต้องใช้เธรดพื้นหลังเลย

อย่างไรก็ตามสมมติว่าคุณจำเป็นต้องจัดการกับข้อยกเว้นจากเธรดลูกอีก ฉันจะใช้ ExecutorService เช่นนี้:

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

พิมพ์

** 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

แต่จะไม่future.get()รอหรือปิดกั้นจนกว่าเธรดจะทำงานเสร็จหรือไม่
Gregor Valentin

@GregorValent ในนั้นให้รอ / บล็อกจนกว่าเธรดจะเสร็จสิ้น Runnable / Callable
Peter Lawrey


3

ใช้Callableแทนของเธรดจากนั้นคุณสามารถเรียกFuture#get()ที่โยนข้อยกเว้นใด ๆ ที่ Callable โยน


1
โปรดทราบว่าข้อยกเว้นที่ส่งออกไปCallable.callจะถูกห่อหุ้มด้วยExcecutionExceptionและสาเหตุของข้อผิดพลาดจะต้องได้รับการประเมิน
Karl Richter

3

ขณะนี้คุณจะจับเฉพาะที่เป็นคลาสย่อยของRuntimeException Exceptionแต่ใบสมัครของคุณอาจจะโยนย่อยชั้นเรียนอื่น ๆ ของข้อยกเว้น จับทั่วไปExceptionนอกเหนือไปจากRuntimeException

เนื่องจากมีการเปลี่ยนแปลงหลายอย่างที่ด้านหน้าเธรดให้ใช้ Java API ขั้นสูง

ต้องการ java.util.concurrent ล่วงหน้าAPIสำหรับแบบมัลติเธรดเหมือนหรือExecutorServiceThreadPoolExecutor

คุณสามารถปรับแต่งThreadPoolExecutorของคุณเพื่อจัดการข้อยกเว้น

ตัวอย่างจากหน้าเอกสารของ Oracle:

แทนที่

protected void afterExecute(Runnable r,
                            Throwable t)

วิธีการเรียกเมื่อเสร็จสิ้นการดำเนินการของ Runnable ที่กำหนด เมธอดนี้ถูกเรียกใช้โดยเธรดที่เรียกใช้งาน หากไม่เป็นโมฆะ Throwable คือ RuntimeException หรือข้อผิดพลาดที่ไม่ถูกจับซึ่งทำให้การดำเนินการสิ้นสุดทันที

รหัสตัวอย่าง:

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

การใช้งาน:

ExtendedExecutor service = new ExtendedExecutor();

ฉันได้เพิ่มหนึ่งตัวสร้างด้านบนของรหัสข้างต้นเป็น:

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

คุณสามารถเปลี่ยน Constructor นี้ให้เหมาะกับความต้องการของคุณตามจำนวนกระทู้

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

2

ฉันประสบปัญหาเดียวกัน ... ทำงานเพียงเล็กน้อย (สำหรับการนำไปใช้ไม่ใช่วัตถุที่ไม่ระบุชื่อ) ... เราสามารถประกาศวัตถุยกเว้นระดับชั้นเป็นโมฆะ ... จากนั้นเริ่มต้นภายในบล็อก catch สำหรับวิธีการเรียกใช้ ... หากมี เป็นข้อผิดพลาดในวิธีการเรียกใช้ตัวแปรนี้จะไม่เป็นโมฆะ .. เราสามารถตรวจสอบค่าว่างสำหรับตัวแปรเฉพาะนี้ได้และถ้ามันไม่เป็นโมฆะก็มีข้อยกเว้นในการประมวลผลเธรด

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

โทร checkForException () หลังจากเข้าร่วม ()


1

คุณเล่นกับ setDefaultUncaughtExceptionHandler () และเมธอดที่เหมือนกันของคลาสเธรดหรือไม่ จาก API: "โดยการตั้งค่าตัวจัดการข้อยกเว้นที่ไม่ถูกตรวจจับเป็นค่าเริ่มต้นแอปพลิเคชันสามารถเปลี่ยนวิธีการจัดการข้อยกเว้นที่ไม่ถูกตรวจจับได้ (เช่นการบันทึกไปยังอุปกรณ์เฉพาะหรือไฟล์) สำหรับเธรดเหล่านั้น ให้ระบบ "

คุณอาจพบคำตอบสำหรับปัญหาของคุณที่นั่นโชคดี! :-)


1

นอกจากนี้จาก Java 8 คุณสามารถเขียนคำตอบ Dan Cruz เป็น:

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

1

AtomicReference ยังเป็นวิธีการแก้ไขข้อผิดพลาดผ่านไปยังเธรดหลักด้วยวิธีการเช่นเดียวกับ 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;
    }  

การเปลี่ยนแปลงเพียงอย่างเดียวคือแทนที่จะสร้างตัวแปรระเหยคุณสามารถใช้ AtomicReference ซึ่งทำสิ่งเดียวกันเบื้องหลัง


0

Threadมันเกือบจะผิดเสมอที่จะขยาย ฉันไม่สามารถระบุได้อย่างชัดเจนเพียงพอ

กฎมัลติเธรด # 1: การขยายThreadไม่ถูกต้อง *

หากคุณดำเนินการRunnableแทนคุณจะเห็นพฤติกรรมที่คาดหวังของคุณ

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

    }
  }
}

ผลิต;

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)

* ยกเว้นกรณีที่คุณต้องการเปลี่ยนวิธีที่แอปพลิเคชันของคุณใช้เธรดซึ่งใน 99.9% ของกรณีที่คุณไม่ทำ หากคุณคิดว่าคุณอยู่ในกรณี 0.1% โปรดดูกฎ # 1


7
สิ่งนี้ไม่ได้รับการยกเว้นในวิธีการหลัก
philwb

การขยายคลาสเธรดนั้นไม่สนับสนุนอย่างยิ่ง ฉันอ่านนี้และคำอธิบายว่าทำไมในการเตรียม OJPC หนังสือ ... เดาว่าพวกเขารู้ว่าพวกเขากำลังพูดถึงอะไร
luigi7up

2
"RuntimeException จาก main" ไม่เคยพิมพ์ที่นี่ .. ยกเว้นไม่ได้ติดอยู่ใน main
Amrish Pandey

0

ถ้าคุณใช้ Thread.UncaughtExceptionHandler ในคลาสที่เริ่มเธรดคุณสามารถตั้งค่าและจากนั้นทำการ rethrow ข้อยกเว้น:

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

}

ซึ่งทำให้เกิดผลลัพธ์ต่อไปนี้:

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)

ไม่จำเป็นต้องทำให้ความสามารถในการเริ่มต้น Throwable ผันผวนเนื่องจาก t.join () จะซิงโครไนซ์
NickL

0

การจัดการข้อยกเว้นในเธรด: ตามค่าเริ่มต้น run () วิธีการจะไม่ส่งข้อยกเว้นใด ๆ ดังนั้นข้อยกเว้นที่ตรวจสอบทั้งหมดภายในวิธีการเรียกใช้จะต้องถูกจับและจัดการที่นั่นเท่านั้นและสำหรับข้อยกเว้นรันไทม์เราสามารถใช้ UncaughtExceptionHandler UncaughtExceptionHandler เป็นอินเตอร์เฟสที่จัดทำโดย Java เพื่อจัดการข้อยกเว้นในวิธีการทำงานของเธรด ดังนั้นเราสามารถใช้อินเทอร์เฟซนี้และตั้งค่าคลาสการใช้งานของเรากลับเป็นวัตถุเธรดโดยใช้เมธอด setUncaughtExceptionHandler () แต่ต้องตั้งค่าตัวจัดการนี้ก่อนที่เราจะเรียกใช้ start () บนดอกยาง

ถ้าเราไม่ได้ตั้งค่า uncaughtExceptionHandler ดังนั้นกลุ่มเธรดเธรดจะทำหน้าที่เป็นตัวจัดการ

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

คำอธิบายที่ดีให้ที่ http://coder2design.com/thread-creation/#exceptions


0

โซลูชันของฉันกับ 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.");
}

0

สำหรับผู้ที่ต้องการหยุดเธรดทั้งหมดที่ทำงานอยู่และรันซ้ำทั้งหมดเมื่อมีข้อใดข้อหนึ่งหยุดทำงาน:

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

    }

}

เพื่อสรุป:

คุณมีฟังก์ชั่นหลักที่สร้างหลายเธรดแต่ละอันมี UncaughtExceptionHandler ซึ่งจะถูกทริกเกอร์โดยข้อยกเว้นใด ๆ ภายในเธรด คุณเพิ่มทุกเธรดในรายการ หาก UncaughtExceptionHandler ถูกทริกเกอร์มันจะวนซ้ำในรายการให้หยุดทุกเธรดและเรียกใช้งานฟังก์ชันหลักทั้งหมดของเธรด


-5

คุณไม่สามารถทำเช่นนี้เพราะมันไม่สมเหตุสมผล หากคุณไม่ได้โทรt.join()มาคุณก็อาจเป็นเธรดหลักได้ทุกที่ในโค้ดเมื่อtเธรดเกิดข้อยกเว้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.