Xử lý gián đoạn ngoại lệ trong Java


317

Sự khác biệt giữa các cách xử lý sau đây là InterruptedExceptiongì? Cách tốt nhất để làm điều đó là gì?

try{
 //...
} catch(InterruptedException e) { 
   Thread.currentThread().interrupt(); 
}

HOẶC LÀ

try{
 //...
} catch(InterruptedException e) {
   throw new RuntimeException(e);
}

EDIT: Tôi cũng muốn biết hai kịch bản này được sử dụng trong trường hợp nào.


Câu trả lời:


436

Sự khác biệt giữa các cách xử lý Interrupttedception sau đây là gì? Cách tốt nhất để làm điều đó là gì?

Có lẽ bạn đã đến để hỏi câu hỏi này bởi vì bạn đã gọi một phương pháp ném InterruptedException.

Trước hết, bạn nên xem throws InterruptedExceptionnó là gì: Một phần của chữ ký phương thức và kết quả có thể có của việc gọi phương thức bạn đang gọi. Vì vậy, bắt đầu bằng cách chấp nhận thực tế rằng an InterruptedExceptionlà kết quả hoàn toàn hợp lệ của lệnh gọi phương thức.

Bây giờ, nếu phương thức bạn gọi ném ngoại lệ như vậy, phương thức của bạn nên làm gì? Bạn có thể tìm ra câu trả lời bằng cách suy nghĩ về những điều sau đây:

Liệu nó có ý nghĩa cho phương pháp bạn đang thực hiện để ném một InterruptedException? Đặt khác nhau, là một InterruptedExceptionkết quả hợp lý khi gọi phương pháp của bạn ?

  • Nếu , thì throws InterruptedExceptionnên là một phần của chữ ký phương thức của bạn và bạn nên để ngoại lệ lan truyền (nghĩa là không bắt được nó).

    Ví dụ : Phương thức của bạn chờ một giá trị từ mạng để hoàn thành tính toán và trả về kết quả. Nếu cuộc gọi mạng chặn ném, InterruptedExceptionphương thức của bạn không thể kết thúc tính toán theo cách thông thường. Bạn để cho InterruptedExceptiontuyên truyền.

    int computeSum(Server server) throws InterruptedException {
        // Any InterruptedException thrown below is propagated
        int a = server.getValueA();
        int b = server.getValueB();
        return a + b;
    }
    
  • Nếu không , thì bạn không nên khai báo phương thức của mình throws InterruptedExceptionvà bạn nên (phải!) Bắt ngoại lệ. Bây giờ có hai điều quan trọng cần ghi nhớ trong tình huống này:

    1. Ai đó làm gián đoạn chủ đề của bạn. Rằng ai đó có lẽ rất muốn hủy bỏ hoạt động, chấm dứt chương trình một cách duyên dáng, hoặc bất cứ điều gì. Bạn nên lịch sự với ai đó và trở về từ phương pháp của bạn mà không cần phải đắn đo thêm.

    2. Mặc dù phương thức của bạn có thể quản lý để tạo ra giá trị trả về hợp lý trong trường hợp InterruptedExceptionthực tế là luồng bị gián đoạn vẫn có thể có tầm quan trọng. Cụ thể, mã gọi phương thức của bạn có thể quan tâm đến việc liệu có xảy ra gián đoạn trong khi thực hiện phương thức của bạn hay không. Do đó, bạn nên ghi lại thực tế một sự gián đoạn đã xảy ra bằng cách đặt cờ bị gián đoạn:Thread.currentThread().interrupt()

    Ví dụ : Người dùng đã yêu cầu in tổng hai giá trị. In " Failed to compute sum" có thể chấp nhận được nếu tổng không thể tính được (và tốt hơn nhiều so với việc để chương trình bị sập với dấu vết ngăn xếp do một InterruptedException). Nói cách khác, không có nghĩa gì khi khai báo phương thức này với throws InterruptedException.

    void printSum(Server server) {
         try {
             int sum = computeSum(server);
             System.out.println("Sum: " + sum);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();  // set interrupt flag
             System.out.println("Failed to compute sum");
         }
    }
    

Bây giờ nên rõ ràng rằng chỉ cần làm throw new RuntimeException(e)là một ý tưởng tồi. Nó không lịch sự với người gọi. Bạn có thể phát minh ra một ngoại lệ thời gian chạy mới nhưng nguyên nhân gốc (ai đó muốn luồng dừng thực thi) có thể bị mất.

Những ví dụ khác:

Triển khaiRunnable : Như bạn có thể đã phát hiện ra, chữ ký của Runnable.runkhông cho phép nhập lại InterruptedExceptions. Vâng, bạn đã đăng ký thực hiện Runnable, có nghĩa là bạn đã đăng ký để đối phó với có thể InterruptedExceptions. Hoặc chọn một giao diện khác, chẳng hạn như Callable, hoặc làm theo cách tiếp cận thứ hai ở trên.

 

Gọi điệnThread.sleep : Bạn đang cố đọc một tệp và thông số kỹ thuật nói rằng bạn nên thử 10 lần với 1 giây ở giữa. Bạn gọi Thread.sleep(1000). Vì vậy, bạn cần phải đối phó với InterruptedException. Đối với một phương pháp như tryToReadFilenó có ý nghĩa hoàn hảo để nói, "Nếu tôi bị gián đoạn, tôi không thể hoàn thành hành động cố gắng đọc tệp" . Nói cách khác, nó có ý nghĩa hoàn hảo cho phương pháp ném InterruptedExceptions.

String tryToReadFile(File f) throws InterruptedException {
    for (int i = 0; i < 10; i++) {
        if (f.exists())
            return readFile(f);
        Thread.sleep(1000);
    }
    return null;
}

Bài viết này đã được viết lại như một bài viết ở đây .


Vấn đề với phương pháp thứ hai là gì?
blitzkriegz

4
Bài báo nói rằng bạn nên gọi interrupt()để duy trì trạng thái bị gián đoạn. Lý do đằng sau không làm điều này trên là Thread.sleep()gì?
oksayt

7
Tôi không đồng ý, trong trường hợp luồng của bạn không hỗ trợ các ngắt, bất kỳ ngắt nào được nhận chỉ ra một điều kiện không mong muốn (ví dụ như lỗi lập trình, sử dụng API xấu) và giải cứu bằng RuntimeException là một phản hồi thích hợp (không nhanh ). Trừ khi nó được coi là một phần của hợp đồng chung của một luồng mà ngay cả khi bạn không hỗ trợ ngắt, bạn vẫn phải tiếp tục hoạt động khi bạn nhận được chúng.
amoe

12
Gọi các phương thức ném InterruptedExceptions và nói rằng bạn "không hỗ trợ ngắt" có vẻ như lập trình cẩu thả và một lỗi đang chờ xảy ra. Đặt khác nhau; Nếu bạn gọi một phương thức được khai báo để ném InterruptedException thì đó không phải là một điều kiện bất ngờ nếu phương thức đó thực tế ném ngoại lệ như vậy.
aioobe

1
@MartiNito, không, gọi Thread.currentThread.interrupt()trong một InterruptedExceptionkhối bắt sẽ chỉ đặt cờ ngắt. Nó sẽ không khiến người khác InterruptedExceptionbị ném / bắt trong InterruptedExceptionkhối bắt bên ngoài .
aioobe

97

Khi điều đó xảy ra, tôi vừa đọc về sáng nay trên đường đi làm việc trong Java Concurrency In Practice của Brian Goetz. Về cơ bản anh ấy nói bạn nên làm một trong ba điều

  1. Tuyên truyềnInterruptedException - Khai báo phương thức của bạn để ném kiểm tra InterruptedExceptionđể người gọi của bạn phải đối phó với nó.

  2. Khôi phục ngắt - Đôi khi bạn không thể ném InterruptedException. Trong những trường hợp này, bạn nên nắm bắt InterruptedExceptionvà khôi phục trạng thái ngắt bằng cách gọi interrupt()phương thức trên currentThreadđể mã cao hơn ngăn xếp cuộc gọi có thể thấy rằng một ngắt đã được phát ra và nhanh chóng quay trở lại từ phương thức. Lưu ý: điều này chỉ có thể áp dụng khi phương thức của bạn có ngữ nghĩa "thử" hoặc "nỗ lực tốt nhất", nghĩa là không có gì quan trọng sẽ xảy ra nếu phương thức không hoàn thành mục tiêu. Ví dụ, log()hoặc sendMetric()có thể là phương pháp như vậy, hoặc boolean tryTransferMoney(), nhưng không void transferMoney(). Xem ở đây để biết thêm chi tiết.

  3. Bỏ qua sự gián đoạn trong phương thức, nhưng khôi phục trạng thái khi thoát - ví dụ: thông qua Guava Uninterruptibles. Uninterruptiblestiếp quản mã soạn sẵn như trong ví dụ Nhiệm vụ không thể thực hiện được trong JCIP § 7.1.3.

10
"Đôi khi bạn không thể ném Interrupttedception" - Tôi nói, đôi khi nó không phù hợp với phương pháp để propagaet Interruptted. Công thức của bạn dường như gợi ý rằng bạn nên điều chỉnh lại Interrupttedception bất cứ khi nào bạn có thể .
aioobe

1
Và nếu bạn đã đọc chương 7, bạn sẽ thấy rằng có một số điểm tinh tế cho điều này, giống như trong Liệt kê 7.7, trong đó một nhiệm vụ không thể thực hiện được sẽ không khôi phục ngắt ngay lập tức , nhưng chỉ sau khi nó được thực hiện. Ngoài ra Goetz có nhiều đồng tác giả của cuốn sách đó ...
Fizz

1
Tôi thích câu trả lời của bạn vì nó ngắn gọn, tuy nhiên tôi tin rằng nó cũng nên nêu lên một cách nhẹ nhàng và nhanh chóng trở lại từ chức năng của bạn trong trường hợp thứ hai. Hãy tưởng tượng một vòng lặp dài (hoặc vô hạn) với một Thread.s ngủ () ở giữa. Việc bắt InterruptedException sẽ gọi Thread.cienThread (). Interrupt () thoát ra khỏi vòng lặp.
Yann Võ

@YannVo Tôi đã chỉnh sửa câu trả lời để áp dụng đề xuất của bạn.
leventov

18

Bạn đang cố làm gì vậy?

InterruptedExceptionđược ném khi một luồng đang chờ hoặc ngủ và một luồng khác làm gián đoạn nó bằng cách sử dụng interruptphương thức trong lớp Thread. Vì vậy, nếu bạn bắt được ngoại lệ này, điều đó có nghĩa là luồng đã bị gián đoạn. Thông thường không có điểm nào để gọi Thread.currentThread().interrupt();lại, trừ khi bạn muốn kiểm tra trạng thái "bị gián đoạn" của chuỗi từ một nơi khác.

Về lựa chọn ném khác của bạn RuntimeException, có vẻ như không phải là một việc rất khôn ngoan (ai sẽ nắm bắt điều này? Làm thế nào để xử lý nó?) Nhưng rất khó để nói thêm nếu không có thêm thông tin.


14
Việc gọi Thread.currentThread().interrupt()đặt cờ bị gián đoạn (một lần nữa), điều này thực sự hữu ích nếu chúng ta muốn đảm bảo rằng ngắt được chú ý và xử lý ở mức cao hơn.
Péter Török

1
@ Péter: Tôi chỉ cập nhật câu trả lời để đề cập đến điều này. Cảm ơn.
Grodriguez

12

Đối với tôi điều quan trọng về vấn đề này là: Interrupttedception không có gì sai, đó là chủ đề làm những gì bạn bảo nó làm. Do đó, việc thiết lập lại nó được gói trong RuntimeException không có ý nghĩa gì.

Trong nhiều trường hợp, việc lấy lại một ngoại lệ được bao bọc trong RuntimeException khi bạn nói, tôi không biết điều gì đã xảy ra ở đây và tôi không thể làm gì để khắc phục nó, tôi chỉ muốn nó thoát khỏi luồng xử lý hiện tại và nhấn bất cứ trình xử lý ngoại lệ nào trên toàn ứng dụng mà tôi có để nó có thể đăng nhập nó. Đó không phải là trường hợp của Interrupttedception, đó chỉ là luồng phản ứng với việc bị gián đoạn () được gọi trên đó, nó ném InterruptedException để giúp hủy quá trình xử lý của luồng một cách kịp thời.

Vì vậy, hãy truyền bá InterruptedException hoặc ăn nó một cách thông minh (nghĩa là tại một nơi mà nó sẽ hoàn thành những gì nó có nghĩa là làm) và đặt lại cờ ngắt. Lưu ý rằng cờ ngắt bị xóa khi InterruptedException bị ném; giả định mà các nhà phát triển thư viện Jdk đưa ra là việc nắm bắt số lượng ngoại lệ để xử lý nó, do đó, theo mặc định, cờ sẽ bị xóa.

Vì vậy, chắc chắn cách đầu tiên là tốt hơn, ví dụ được đăng thứ hai trong câu hỏi không hữu ích trừ khi bạn không mong đợi chủ đề thực sự bị gián đoạn và làm gián đoạn nó gây ra lỗi.

Đây là một câu trả lời tôi đã viết mô tả cách thức hoạt động của các ngắt, với một ví dụ . Bạn có thể thấy trong mã ví dụ nơi nó đang sử dụng InterruptedException để bảo lãnh cho một vòng lặp while trong phương thức chạy của Runnable.


10

Lựa chọn mặc định chính xác là thêm InterruptedException vào danh sách ném của bạn. Một ngắt cho biết rằng một chủ đề khác muốn chủ đề của bạn kết thúc. Lý do cho yêu cầu này không được đưa ra rõ ràng và hoàn toàn theo ngữ cảnh, vì vậy nếu bạn không có thêm kiến ​​thức nào, bạn nên cho rằng đó chỉ là tắt máy thân thiện và bất cứ điều gì tránh được việc tắt máy là phản ứng không thân thiện.

Java sẽ không ném ngẫu nhiên InterruptedException, mọi lời khuyên sẽ không ảnh hưởng đến ứng dụng của bạn nhưng tôi đã gặp phải trường hợp nhà phát triển theo chiến lược "nuốt" trở nên rất bất tiện. Một nhóm đã phát triển một loạt các thử nghiệm và sử dụng Thread.S ngủ rất nhiều. Bây giờ chúng tôi bắt đầu chạy thử nghiệm trong máy chủ CI của mình và đôi khi do lỗi trong mã sẽ bị kẹt trong chờ đợi vĩnh viễn. Để làm cho tình hình tồi tệ hơn, khi cố gắng hủy công việc CI, nó không bao giờ đóng vì Thread.Interrupt dự định hủy bỏ bài kiểm tra đã không hủy bỏ công việc. Chúng tôi đã phải đăng nhập vào hộp và tự hủy các quy trình.

Câu chuyện dài quá ngắn, nếu bạn chỉ cần ném Interrupttedception, bạn sẽ phù hợp với mục đích mặc định mà chủ đề của bạn sẽ kết thúc. Nếu bạn không thể thêm Interrupttedception vào danh sách ném của mình, tôi sẽ bọc nó trong RuntimeException.

Có một lập luận rất hợp lý được đưa ra rằng InterruptedException phải là một RuntimeException, vì điều đó sẽ khuyến khích việc xử lý "mặc định" tốt hơn. Đây không phải là RuntimeException chỉ vì các nhà thiết kế mắc kẹt với một quy tắc phân loại rằng RuntimeException sẽ thể hiện lỗi trong mã của bạn. Vì Interrupttedception không phát sinh trực tiếp từ một lỗi trong mã của bạn, nên không phải vậy. Nhưng thực tế là thường xảy ra Interrupttedception vì có lỗi trong mã của bạn, (tức là vòng lặp vô tận, khóa chết) và Interrupt là một phương thức khác của luồng để xử lý lỗi đó.

Nếu bạn biết có việc dọn dẹp hợp lý sẽ được thực hiện, thì hãy làm điều đó. Nếu bạn biết nguyên nhân sâu xa hơn của Ngắt, bạn có thể xử lý toàn diện hơn.

Vì vậy, tóm lại các lựa chọn của bạn để xử lý nên theo danh sách này:

  1. Theo mặc định, thêm để ném.
  2. Nếu không được phép thêm vào các cú ném, hãy ném RuntimeException (e). (Lựa chọn tốt nhất cho nhiều lựa chọn xấu)
  3. Chỉ khi bạn biết một nguyên nhân rõ ràng của Ngắt, hãy xử lý như mong muốn. Nếu việc xử lý của bạn là cục bộ đối với phương thức của bạn, thì thiết lập lại bị gián đoạn bởi một cuộc gọi đến Thread.cienThread (). Interrupt ().

3

Tôi chỉ muốn thêm một lựa chọn cuối cùng cho những gì hầu hết mọi người và bài viết đề cập. Như mR_fr0g đã nêu, điều quan trọng là phải xử lý ngắt một cách chính xác bằng cách:

  • Tuyên truyền về InterruptException

  • Khôi phục trạng thái ngắt trên luồng

Hoặc thêm vào:

  • Xử lý tùy chỉnh ngắt

Không có gì sai khi xử lý ngắt theo cách tùy chỉnh tùy thuộc vào hoàn cảnh của bạn. Vì một ngắt là một yêu cầu chấm dứt, trái với một lệnh mạnh mẽ, việc hoàn thành công việc bổ sung để cho phép ứng dụng xử lý yêu cầu một cách duyên dáng là hoàn toàn hợp lệ. Ví dụ: nếu một luồng đang ngủ, chờ IO hoặc phản hồi phần cứng, khi nó nhận được ngắt, thì việc đóng một cách duyên dáng bất kỳ kết nối nào trước khi chấm dứt luồng.

Tôi đặc biệt khuyên bạn nên hiểu chủ đề này, nhưng bài viết này là một nguồn thông tin tốt: http://www.ibm.com/developerworks/java/l Library / j-jtp05236 /


0

Tôi sẽ nói trong một số trường hợp không có gì để làm. Có thể không phải là điều bạn nên làm theo mặc định, nhưng trong trường hợp không có cách nào để gián đoạn xảy ra, tôi không chắc chắn phải làm gì khác (có thể là lỗi đăng nhập, nhưng điều đó không ảnh hưởng đến luồng chương trình).

Một trường hợp sẽ là trong trường hợp bạn có một hàng đợi nhiệm vụ (chặn). Trong trường hợp bạn có một daemon Thread xử lý các tác vụ này và bạn không tự mình ngắt luồng (theo hiểu biết của tôi, jvm không làm gián đoạn các luồng daemon khi tắt jvm), tôi thấy không có cách nào để gián đoạn xảy ra, và do đó nó có thể xảy ra chỉ cần bỏ qua. (Tôi biết rằng một chủ đề daemon có thể bị giết bởi jvm bất cứ lúc nào và do đó không phù hợp trong một số trường hợp).

EDIT: Một trường hợp khác có thể là các khối được bảo vệ, ít nhất là dựa trên hướng dẫn của Oracle tại: http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html


Đây là lời khuyên tồi. Tôi gặp khó khăn cực độ khi nghĩ về bất kỳ nhiệm vụ nào quan trọng đến mức bỏ qua Ngắt. Oracle có thể chỉ lười biếng và không muốn thêm (nhiều hơn) sự lộn xộn vào lệnh gọi Thread.s ngủ (). Trừ khi bạn biết lý do tại sao chủ đề của bạn bị gián đoạn, bạn nên dừng bất cứ điều gì bạn đang làm (trở lại hoặc suy nghĩ lại một ngoại lệ để giúp chủ đề của bạn chết nhanh nhất có thể).
Ajax

Đó là lý do tại sao đôi khi tôi nói. Cũng bởi vì nó chưa được đề xuất và nó hoàn toàn ổn trong một số trường hợp theo ý kiến ​​của tôi. Tôi đoán nó phụ thuộc vào loại phần mềm bạn đang xây dựng, nhưng nếu bạn có một luồng công nhân 24/7 không bao giờ dừng lại (ngoài việc tắt JVM), tôi sẽ coi các ngắt từ ví dụ như lấy một mục từ BlockingQueue là một lỗi (tức là nhật ký) vì "điều đó không nên xảy ra" và thử lại. Của nguồn tôi sẽ có các cờ riêng để kiểm tra chấm dứt chương trình. Trong một số chương trình của tôi, JVM tắt không phải là điều nên xảy ra trong hoạt động bình thường.
Bjarne Boström

Hoặc đặt theo một cách khác: Theo tôi, tốt hơn là có nguy cơ có thêm một báo cáo nhật ký lỗi (và ví dụ: có thư được gửi trên nhật ký lỗi) hơn là có một ứng dụng nên chạy 24/7 để dừng vì bị gián đoạn, vì vậy Tôi thấy không có cách nào xảy ra trong điều kiện bình thường.
Bjarne Boström

Hừm, các trường hợp sử dụng của bạn có thể khác nhau, nhưng trong một thế giới lý tưởng, bạn không chỉ chấm dứt vì bạn bị gián đoạn. Bạn dừng công việc ra khỏi hàng đợi và để cho chủ đề đó chết. Nếu bộ lập lịch của các luồng cũng bị gián đoạn và được yêu cầu tắt, thì JVM sẽ chấm dứt và trò chơi của nó kết thúc. Cụ thể, nếu bạn gửi kill -9 hoặc đang cố gắng gỡ bỏ máy chủ thư của bạn hoặc bất cứ điều gì, thì nó sẽ bỏ qua bạn cho đến khi bạn buộc thoát, do đó ngăn các phần khác trong mã của bạn tắt máy một cách duyên dáng. Buộc giết trong khi ghi vào đĩa hoặc db và tôi chắc chắn những điều tuyệt vời sẽ xảy ra.
Ajax
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.