Làm thế nào để bắt một ngoại lệ từ một chủ đề


165

Tôi có lớp chính Java, trong lớp, tôi bắt đầu một luồng mới, trong lớp chính, nó đợi cho đến khi luồng chết. Tại một số thời điểm, tôi ném một ngoại lệ thời gian chạy từ luồng, nhưng tôi không thể bắt ngoại lệ được ném từ luồng trong lớp chính.

Đây là mã:

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

    }
  }
}

Có ai biết tại sao không?

Câu trả lời:


220

Sử dụng 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();

13
Tôi có thể làm gì, nếu tôi muốn ném ngoại lệ lên cấp trên?
Rodi

6
@rodi lưu ex vào một biến dễ bay hơi mà cấp trên có thể thấy trong trình xử lý (ví dụ: biến thành viên). Bên ngoài, kiểm tra nếu null, nếu không ném. Hoặc mở rộng UEH với một trường biến động mới và lưu trữ ngoại lệ ở đó.
Ciro Santilli 郝海东 冠状 病 事件 法轮功

1
Tôi muốn bắt một ngoại lệ từ bên trong chủ đề của mình - mà không bị dừng lại. Điều này bằng cách nào đó sẽ được sử dụng?
Lealo

42

Đó là vì các ngoại lệ là cục bộ của một luồng và luồng chính của bạn không thực sự nhìn thấy runphương thức. Tôi đề nghị bạn đọc thêm về cách hoạt động của luồng, nhưng để nhanh chóng tóm tắt: cuộc gọi của bạn để startbắt đầu một luồng khác, hoàn toàn không liên quan đến luồng chính của bạn. Cuộc gọi joinchỉ đơn giản là chờ nó được thực hiện. Một ngoại lệ được ném vào một luồng và không bao giờ bị bắt kết thúc nó, đó là lý do tại sao jointrả về luồng chính của bạn, nhưng chính ngoại lệ đó bị mất.

Nếu bạn muốn biết về những ngoại lệ chưa được bắt gặp này, bạn có thể thử điều này:

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

Thông tin thêm về xử lý ngoại lệ chưa được phát hiện có thể được tìm thấy ở đây .


Tôi thích điều đó! Đặt trình xử lý bằng phương thức tĩnh Thread.setDefaultUncaughtExceptionHandler()cũng bắt được các ngoại lệ trong luồng "chính"
Teo J.


23

Nhiều khả năng;

  • bạn không cần truyền ngoại lệ từ luồng này sang luồng khác.
  • nếu bạn muốn xử lý một ngoại lệ, chỉ cần thực hiện nó trong luồng đã ném nó.
  • chủ đề chính của bạn không cần phải đợi từ chủ đề nền trong ví dụ này, điều đó thực sự có nghĩa là bạn hoàn toàn không cần một chủ đề nền.

Tuy nhiên, giả sử bạn cần xử lý một ngoại lệ từ một luồng con khác. Tôi sẽ sử dụng một ExecutorService như thế này:

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

in

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

Nhưng không future.get()chờ đợi hay chặn cho đến khi luồng hoàn thành?
Gregor Valentin ngày

@GregorValentin nó chờ / chặn cho đến khi chuỗi kết thúc Runnable / Callable.
Peter Lawrey


3

Sử dụng Callablethay vì Thread, sau đó bạn có thể gọi Future#get()đó ném bất kỳ ngoại lệ nào mà Callable đã ném.


1
Lưu ý rằng ngoại lệ ném bên trong Callable.callđược bọc trong một ExcecutionExceptionvà nguyên nhân của nó phải được đánh giá.
Karl Richter

3

Hiện tại bạn chỉ đang bắt RuntimeException, một lớp phụ của Exception. Nhưng ứng dụng của bạn có thể ném các lớp ngoại lệ khác . Bắt chung chung ExceptionngoàiRuntimeException

Vì nhiều thứ đã được thay đổi ở phía trước Threading, hãy sử dụng API java nâng cao.

Ưu tiên API java.util.conc hiện tại cho đa luồng như ExecutorServicehoặc ThreadPoolExecutor.

Bạn có thể tùy chỉnh ThreadPoolExecutor để xử lý các trường hợp ngoại lệ.

Ví dụ từ trang tài liệu oracle:

Ghi đè

protected void afterExecute(Runnable r,
                            Throwable t)

Phương thức được gọi sau khi hoàn thành việc thực thi Runnable đã cho. Phương thức này được gọi bởi luồng thực thi tác vụ. Nếu không có giá trị, thì throwable là RuntimeException hoặc Error chưa được xử lý khiến cho việc thực thi bị chấm dứt đột ngột.

Mã ví dụ:

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

Sử dụng:

ExtendedExecutor service = new ExtendedExecutor();

Tôi đã thêm một hàm tạo trên đầu mã ở trên là:

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

Bạn có thể thay đổi hàm tạo này cho phù hợp với yêu cầu của bạn về số lượng luồng.

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

2

Tôi đã đối mặt với cùng một vấn đề ... công việc nhỏ xung quanh (chỉ để thực hiện không phải đối tượng ẩn danh) ... chúng ta có thể khai báo đối tượng ngoại lệ cấp lớp là null ... sau đó khởi tạo nó bên trong khối bắt cho phương thức chạy ... nếu có là lỗi trong phương thức chạy, biến này sẽ không null .. sau đó chúng ta có thể kiểm tra null cho biến cụ thể này và nếu nó không null thì có ngoại lệ trong thực thi luồng.

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

gọi checkForException () sau khi tham gia ()


1

Bạn có chơi xung quanh với setDefaultUncaughtExceptionHandler () và các phương thức giống nhau của lớp Thread không? Từ API: "Bằng cách đặt trình xử lý ngoại lệ chưa được mặc định, ứng dụng có thể thay đổi cách xử lý các ngoại lệ chưa được xử lý (như đăng nhập vào một thiết bị cụ thể hoặc tệp) cho các luồng đã chấp nhận bất kỳ hành vi" mặc định "nào hệ thống cung cấp. "

Bạn có thể tìm thấy câu trả lời cho vấn đề của mình ở đó ... chúc may mắn! :-)


1

Cũng từ Java 8, bạn có thể viết câu trả lời Dan Cruz như:

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 cũng là một giải pháp để chuyển lỗi sang luồng chính. Cách tiếp cận tương tự như của 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;
    }  

Thay đổi duy nhất là thay vì tạo một biến dễ bay hơi, bạn có thể sử dụng AtomicReference đã làm điều tương tự đằng sau hậu trường.


0

Nó gần như luôn luôn sai để mở rộng Thread. Tôi không thể nói điều này đủ mạnh.

Quy tắc đa luồng số 1: Mở rộng Threadlà sai. *

Nếu bạn thực hiện Runnablethay vì bạn sẽ thấy hành vi dự kiến ​​của bạn.

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

    }
  }
}

sản xuất;

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)

* trừ khi bạn muốn thay đổi cách ứng dụng của bạn sử dụng các luồng, trong 99,9% trường hợp bạn không làm. Nếu bạn nghĩ rằng bạn nằm trong 0,1% các trường hợp, vui lòng xem quy tắc số 1.


7
Điều này không bắt được ngoại lệ trong phương thức chính.
philwb

Mở rộng lớp Thread được khuyến khích mạnh mẽ. Tôi đọc điều này và giải thích tại sao trong phần chuẩn bị OJPC. cuốn sách ... Đoán xem, họ biết những gì họ đang nói về
luigi7up

2
"RuntimeException from main" không bao giờ được in ở đây .. ngoại lệ không bị bắt trong chính
Amrish Pandey

0

Nếu bạn triển khai Thread.UncaughtExceptionHandler trong lớp bắt đầu Chủ đề, bạn có thể đặt và sau đó lấy lại ngoại lệ:

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

}

Điều này gây ra đầu ra sau đây:

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)

Không cần phải làm cho initException có thể biến động, vì t.join () sẽ đồng bộ hóa.
NickL 17/03/2015

0

Xử lý ngoại lệ trong luồng: Theo mặc định, phương thức run () không đưa ra bất kỳ ngoại lệ nào, vì vậy tất cả các ngoại lệ được kiểm tra bên trong phương thức chạy phải được bắt và xử lý ở đó và đối với ngoại lệ thời gian chạy, chúng ta có thể sử dụng UncaughtExceptionHandler. UncaughtExceptionHandler là một giao diện được cung cấp bởi Java để xử lý các ngoại lệ trong phương thức chạy Thread. Vì vậy, chúng ta có thể thực hiện giao diện này và đặt lại lớp triển khai của chúng ta trở lại đối tượng Thread bằng phương thức setUncaughtExceptionHandler (). Nhưng trình xử lý này phải được đặt trước khi chúng ta gọi start () trên guồng.

nếu chúng ta không đặt unsaughtExceptionHandler thì Thread Threadgroup hoạt động như một trình xử lý.

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

Lời giải thích hay được đưa ra tại http://coder2design.com/thread-creation/#exceptions


0

Giải pháp của tôi với 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

Đối với những người cần dừng tất cả Chủ đề đang chạy và chạy lại tất cả Chủ đề khi bất kỳ Chủ đề nào bị dừng trong Ngoại lệ:

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

    }

}

Tóm lại :

Bạn có một chức năng chính tạo ra nhiều luồng, mỗi luồng có UncaughtExceptionHandler, được kích hoạt bởi bất kỳ Ngoại lệ nào bên trong một luồng. Bạn thêm mỗi Chủ đề vào Danh sách. Nếu UncaughtExceptionHandler được kích hoạt, nó sẽ lặp qua Danh sách, dừng mọi Chủ đề và khởi chạy lại chức năng chính giải trí tất cả các Chủ đề.


-5

Bạn không thể làm điều này, vì nó không thực sự có ý nghĩa. Nếu bạn chưa gọi t.join()thì luồng chính của bạn có thể ở bất kỳ đâu trong mã khi tluồng đó ném ngoại lệ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.