I. Đa luồng và Concurrency (đồng thời) trong Java
1. Sự khác biệt giữa Process và Thread là gì?
Process(quy trình) là một môi trường thực thi khép kín và nó có thể được xem như một chương trình hoặc ứng dụng trong khi Thread là một tác vụ thực thi đơn lẻ trong quy trình. Java runtime enviroment chạy như một quy trình duy nhất chứa các lớp và chương trình khác nhau dưới dạng các Process. Thread có thể được gọi là quá trình nhẹ. Thread yêu cầu ít tài nguyên hơn để tạo và tồn tại trong quy trình, Thread chia sẻ tài nguyên Process.
2. Lợi ích của lập trình đa luồng là gì?
Trong lập trình đa luồng, nhiều luồng đang thực thi đồng thời để cải thiện hiệu suất vì CPU không rảnh trong trường hợp một số luồng đang chờ lấy một số tài nguyên. Nhiều luồng chia sẻ bộ nhớ heap, vì vậy tốt hơn là bạn nên tạo nhiều luồng để thực hiện một số tác vụ hơn là tạo nhiều quy trình. Ví dụ, Servlet có hiệu suất tốt hơn CGI vì Servlet hỗ trợ đa luồng nhưng CGI thì không.
3. Sự khác biệt giữa Thread của người dùng và Thread của daemon là gì?
Khi chúng ta tạo một Thread trong chương trình java, nó được gọi là luồng người dùng. Một Thread daemon chạy trong nền và không ngăn JVM kết thúc. Khi không có luồng người dùng nào đang chạy, JVM sẽ tắt chương trình và thoát. Một luồng con được tạo từ Thread daemon cũng là một Thread daemon.
4. Làm thế nào chúng ta có thể tạo một Thread trong Java?
Có hai cách để tạo Thread trong Java - đầu tiên bằng cách triển khai interface Runnable và sau đó tạo một đối tượng Thread từ nó và thứ hai là mở rộng Lớp Thread. Đọc bài đăng này để tìm hiểu thêm về cách creating threads in java.
5. Các trạng thái khác nhau trong vòng đời của Thread là gì?
Khi chúng ta tạo một Thread trong chương trình java, trạng thái của nó là New. Sau đó, chúng tôi bắt đầu luồng thay đổi trạng thái của nó thành Runnable. Thread Scheduler chịu trách nhiệm phân bổ CPU cho các luồng trong nhóm luồng Runnable và thay đổi trạng thái của chúng thành Running. Các trạng thái Chủ đề khác là Waiting, Blocked và Dead. Đọc bài đăng này để tìm hiểu thêm về life cycle of thread. \
[sociallocker id = ”2713 ″]
6. Chúng ta có thể gọi phương thức run() của một lớp Thread không?
Có, chúng ta có thể gọi phương thức run() của một lớp Thread nhưng sau đó nó sẽ hoạt động như một phương thức bình thường. Để thực sự thực thi nó trong một Thread, chúng ta cần khởi động nó bằng phương thức Thread.start().
7. Làm thế nào chúng ta có thể tạm dừng việc thực hiện một Thread trong thời gian cụ thể?
Chúng ta có thể sử dụng phương thức sleep() của lớp Thread để tạm dừng việc thực thi của Thread trong một thời gian nhất định. Lưu ý rằng điều này sẽ không dừng quá trình xử lý luồng trong thời gian cụ thể, khi luồng thức dậy từ chế độ ngủ, trạng thái của nó sẽ được thay đổi thành có thể chạy được và dựa trên lập lịch luồng, và nó sẽ được thực thi.
8. Bạn hiểu gì về Thread Priority?
Mọi luồng đều có một mức độ ưu tiên, thường thì luồng có mức độ ưu tiên cao hơn được ưu tiên thực thi nhưng nó phụ thuộc vào việc triển khai Thread Scheduler phụ thuộc vào HĐH. Chúng ta có thể chỉ định mức độ ưu tiên của luồng nhưng nó không đảm bảo rằng luồng có mức độ ưu tiên cao hơn sẽ được thực thi trước luồng có mức ưu tiên thấp hơn. Ưu tiên luồng là một int có giá trị thay đổi từ 1 đến 10 trong đó 1 là luồng có mức ưu tiên thấp nhất và 10 là luồng có mức ưu tiên cao nhất.
9. Thread Scheduler và Time Slicing là gì?
Thread Scheduler là dịch vụ Hệ điều hành phân bổ thời gian CPU cho các luồng có thể chạy được. Khi chúng ta tạo và bắt đầu một luồng, việc thực thi của nó phụ thuộc vào việc triển khai Thread Scheduler. Time Slicing là quá trình phân chia thời gian có sẵn của CPU cho các luồng có thể chạy được. Việc phân bổ thời gian CPU cho các luồng có thể dựa trên mức độ ưu tiên của luồng hoặc luồng chờ thời gian lâu hơn sẽ được ưu tiên hơn trong việc nhận thời gian CPU. Lập lịch luồng không thể được java kiểm soát, vì vậy tốt hơn hết là bạn nên kiểm soát nó từ chính ứng dụng.
10. Chuyển đổi ngữ cảnh trong multi-threading là gì?
Chuyển mạch theo ngữ cảnh là quá trình lưu trữ và khôi phục trạng thái CPU để quá trình thực thi Luồng có thể được tiếp tục từ cùng một điểm tại một thời điểm sau đó. Chuyển đổi ngữ cảnh là tính năng cần thiết cho hệ điều hành đa nhiệm và hỗ trợ cho môi trường đa luồng.
11. Làm thế nào chúng ta có thể đảm bảo rằng main() là luồng cuối cùng kết thúc trong Chương trình Java?
Chúng ta có thể sử dụng phương thức Thread join() để đảm bảo rằng tất cả các luồng do chương trình tạo ra đã chết trước khi kết thúc chức năng chính. Đây là một bài viết về Thread join method.
12. Làm thế nào để các luồng giao tiếp với nhau?
Khi các luồng chia sẻ tài nguyên, giao tiếp giữa các luồng là rất quan trọng để phối hợp các nỗ lực của họ. Các phương thức của lớp đối tượng wait(), Inform() và InformAll() cho phép các luồng giao tiếp về trạng thái khóa của một tài nguyên. Kiểm tra bài đăng này để tìm hiểu thêm về thread wait, notify and notifyAll.
Khi các luồng chia sẻ tài nguyên, giao tiếp giữa các luồng là rất quan trọng để phối hợp các nỗ lực của họ. Các phương thức của lớp đối tượng wait(), Inform() và InformAll() cho phép các luồng giao tiếp về trạng thái khóa của một tài nguyên. Kiểm tra bài đăng này để tìm hiểu thêm về thread wait, notify and notifyAll.
13. Tại sao các phương thức giao tiếp luồng wait(), notify() và notifyAll() nằm trong lớp Object?
Trong Java mọi đối tượng đều có một màn hình và chờ, các phương thức thông báo được sử dụng để chờ bộ theo dõi Object hoặc để thông báo cho các luồng khác rằng bộ theo dõi Object hiện đang rảnh. Không có trình giám sát các luồng trong java và đồng bộ hóa có thể được sử dụng với bất kỳ Object nào, đó là lý do tại sao nó là một phần của lớp Object để mọi lớp trong java đều có các phương thức thiết yếu này để giao tiếp giữa các luồng.
14. Tại sao các phương thức wait(), notify() và notifyAll() phải được gọi từ phương thức hoặc khối được đồng bộ hóa?
Khi một Thread gọi gọi wait() trên bất kỳ Object nào, nó phải có giám sát trên Object mà nó sẽ rời khỏi và chuyển sang trạng thái chờ cho đến khi bất kỳ cuộc gọi luồng nào khác notify() trên Object này. Tương tự như vậy khi một luồng gọi notify ( ) trên bất kỳ Object nào, nó sẽ rời màn hình trên Object và các luồng chờ khác có thể lấy màn hình trên Object. Vì tất cả các phương thức này yêu cầu Thread phải có Object monitor, điều đó chỉ có thể đạt được bằng cách đồng bộ hóa, chúng cần được gọi từ phương thức hoặc khối được đồng bộ hóa.
15. Tại sao các phương thức sleep() và yield() của Thread là static?
Các phương thức sleep() và yield() của luồng hoạt động trên luồng hiện đang thực thi. Vì vậy, không có ích gì khi gọi các phương thức này trên một số luồng khác đang ở trạng thái chờ. Đó là lý do tại sao các phương thức này được tạo tĩnh để khi phương thức này được gọi là static, nó hoạt động trên luồng thực thi hiện tại và tránh nhầm lẫn cho các lập trình viên, những người có thể nghĩ rằng họ có thể gọi các phương thức này trên một số luồng không chạy.
Các phương thức sleep() và yield() của luồng hoạt động trên luồng hiện đang thực thi. Vì vậy, không có ích gì khi gọi các phương thức này trên một số luồng khác đang ở trạng thái chờ. Đó là lý do tại sao các phương thức này được tạo tĩnh để khi phương thức này được gọi là static, nó hoạt động trên luồng thực thi hiện tại và tránh nhầm lẫn cho các lập trình viên, những người có thể nghĩ rằng họ có thể gọi các phương thức này trên một số luồng không chạy.
16. Làm thế nào chúng ta có thể đạt được an toàn luồng trong Java?
Có một số cách để đạt được an toàn luồng trong java - đồng bộ hóa, các lớp đồng thời nguyên tử, triển khai interface Lock đồng thời, sử dụng từ khóa dễ bay hơi, sử dụng các lớp không thay đổi và các lớp an toàn luồng. Tìm hiểu thêm tại thread safety tutorial.
17. Từ khóa volatile trong Java là gì?
Khi chúng ta sử dụng từ khóa volatile với một biến, tất cả các luồng đọc giá trị của nó trực tiếp từ bộ nhớ và không lưu vào bộ nhớ đệm. Điều này đảm bảo rằng giá trị được đọc giống như trong bộ nhớ.
18. Cái nào được ưu tiên hơn - Synchronized method hay synchronized block?
synchronized block (Khối đồng bộ hóa) là cách được ưa thích hơn vì nó không khóa Object, các phương thức đồng bộ hóa sẽ khóa Object và nếu có nhiều khối đồng bộ hóa trong lớp, mặc dù chúng không liên quan, nó sẽ ngăn chúng thực thi và đặt chúng ở trạng thái chờ. để lấy khóa đối tượng.
19. Cách tạo luồng daemon trong Java?
Lớp Thread setDaemon (true) có thể được sử dụng để tạo luồng daemon trong java. Chúng ta cần gọi phương thức này trước khi gọi phương thức start() nếu không nó sẽ ném IllegalThreadStateException.
20. ThreadLocal là gì?
Java ThreadLocal được sử dụng để tạo các biến cục bộ của luồng. Chúng ta biết rằng tất cả các luồng của một Đối tượng đều chia sẻ các biến của nó , vì vậy nếu biến đó không an toàn cho luồng, chúng ta có thể sử dụng đồng bộ hóa nhưng nếu chúng ta muốn tránh đồng bộ hóa, chúng ta có thể sử dụng các biến ThreadLocal.
Mỗi luồng đều có biến ThreadLocal riêng và họ có thể sử dụng các phương thức get() và set() của nó để nhận giá trị mặc định hoặc thay đổi giá trị cục bộ của nó thành Thread. Các cá thể ThreadLocal thường là các trường private static trong các lớp muốn liên kết trạng thái với một luồng. Kiểm tra bài đăng này để biết chương trình ví dụ nhỏ hiển thị ThreadLocal Example.
21. Thread Group là gì? Tại sao nó được khuyên không nên sử dụng nó?
ThreadGroup là một lớp nhằm cung cấp thông tin về một nhóm luồng. API ThreadGroup yếu và nó không có bất kỳ chức năng nào không được cung cấp bởi Thread. Hai trong số các tính năng chính mà nó có là lấy danh sách các luồng đang hoạt động trong một nhóm luồng và thiết lập trình xử lý ngoại lệ không cần thiết cho luồng. Nhưng Java 1.5 đã thêm phương thức setUncaughtExceptionHandler ( UncaughtExceptionHandler eh) bằng cách sử dụng phương thức này, chúng ta có thể thêm trình xử lý ngoại lệ chưa có vào luồng. Vì vậy, ThreadGroup đã lỗi thời và do đó không được khuyến khích sử dụng nữa.
t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler(){
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("exception occured:"+e.getMessage());
}
});
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("exception occured:"+e.getMessage());
}
});
22. Java Thread Dump là gì, Làm thế nào chúng ta có thể lấy Java Thread dump của một chương trình?
Thread Dump (Kết xuất luồng) là danh sách tất cả các luồng đang hoạt động trong JVM, kết xuất luồng rất hữu ích trong việc phân tích tắc nghẽn trong ứng dụng và phân tích các tình huống bế tắc. Có nhiều cách sử dụng mà chúng ta có thể tạo kết xuất luồng - Sử dụng Profiler, lệnh Kill -3, công cụ jstack, v.v. Tôi thích công cụ jstack để tạo kết xuất luồng của một chương trình vì nó dễ sử dụng và đi kèm với cài đặt JDK. Vì đây là một công cụ dựa trên thiết bị đầu cuối, chúng tôi có thể tạo tập lệnh để tạo kết xuất chuỗi theo các khoảng thời gian đều đặn để phân tích nó sau này. Đọc bài đăng này để biết thêm về cách generating thread dump in java.
23. Deadlock là gì? Làm thế nào để phân tích và tránh tình trạng bế tắc?
Deadlock là một tình huống lập trình mà hai hoặc nhiều luồng bị chặn mãi mãi, tình huống này phát sinh với ít nhất hai luồng và hai hoặc nhiều tài nguyên.
Để phân tích bế tắc, chúng ta cần xem kết xuất luồng java của ứng dụng, chúng ta cần tìm các luồng có trạng thái là BLOCKED và sau đó là các tài nguyên mà nó đang chờ khóa, mọi tài nguyên có một ID duy nhất bằng cách sử dụng mà chúng ta có thể tìm thấy luồng nào đã giữ khóa trên đối tượng.
Tránh lồng nhau khóa, chỉ khóa những gì cần thiết và Tránh chờ đợi vô thời hạn là những cách phổ biến để tránh tình trạng bế tắc, hãy đọc bài đăng này để tìm hiểu cách analyze deadlock in java với chương trình mẫu.
24. Java Timer Class là gì? Làm thế nào để lên lịch một nhiệm vụ để chạy sau khoảng thời gian cụ thể?
java.util.Timer là một lớp tiện ích có thể được sử dụng để lên lịch thực thi một luồng tại một thời điểm nhất định trong tương lai. Lớp Timer của Java có thể được sử dụng để lên lịch cho một tác vụ được chạy một lần hoặc được chạy theo các khoảng thời gian đều đặn.
java.util.TimerTask là một lớp trừu tượng thực hiện interface Runnable và chúng ta cần mở rộng lớp này để tạo TimerTask của riêng mình có thể được lên lịch sử dụng lớp java Timer.
Kiểm tra bài đăng này để biết java Timer example.
25. Thread Pool là gì? Làm cách nào chúng ta có thể tạo Thread Pool trong Java?
Nhóm luồng quản lý nhóm luồng công nhân, nó chứa một hàng đợi giữ các tác vụ chờ được thực thi.
Một nhóm luồng quản lý tập hợp các luồng Runnable và các luồng công nhân thực thi Runnable từ hàng đợi.
java.util.concurrent.Executor cung cấp việc triển khai interface java.util.concurrent.Executor để tạo nhóm luồng trong java. Chương trình Thread Pool Example chỉ ra cách tạo và sử dụng Thread Pool trong java. Hoặc đọc ScheduledThreadPoolExecutor Example để biết cách lên lịch các tác vụ sau độ trễ nhất định.
26. Điều gì sẽ xảy ra nếu chúng ta không ghi đè phương thức run() của lớp Thread ?
Mã phương thức run() của lớp luồng như hình dưới đây.
public void run() {
if (target != null) {
target.run();
}
}
Mục tiêu trên được đặt trong phương thức init() của lớp Thread và nếu chúng ta tạo một thể hiện của lớp Thread dưới dạng TestThread() mới, thì nó được đặt thành null. Vì vậy, sẽ không có gì xảy ra nếu chúng ta không ghi đè phương thức run(). Dưới đây là một ví dụ đơn giản chứng minh điều này.}
}
public class TestThread extends Thread {
//not overriding Thread.run() method
//main method, can be in other class too public static void main(String args[]){
Thread t = new TestThread();
System.out.println("Before starting thread");
t.start();
System.out.println("After starting thread");
} }
//not overriding Thread.run() method
//main method, can be in other class too public static void main(String args[]){
Thread t = new TestThread();
System.out.println("Before starting thread");
t.start();
System.out.println("After starting thread");
} }
Nó sẽ chỉ in dưới đầu ra và kết thúc.
Before starting thread
After starting thread
[/ sociallocker]
II. Phỏng vấn Java Concurrency
1. Hoạt động của atomic là gì? Các lớp atomic trong Java Concurrency API là gì?
Các hoạt động atomic được thực hiện trong một đơn vị nhiệm vụ duy nhất mà không bị can thiệp bởi các hoạt động khác. Các hoạt động atomic là cần thiết trong môi trường đa luồng để tránh dữ liệu không nhất quán.
int ++ không phải là một phép toán atomic. Vì vậy, vào thời điểm một luồng đọc giá trị của nó và tăng nó lên một, luồng khác đã đọc giá trị cũ hơn dẫn đến kết quả sai.
Để giải quyết vấn đề này, chúng tôi sẽ phải đảm bảo rằng hoạt động gia tăng trên số đếm là atomic, chúng tôi có thể làm điều đó bằng cách sử dụng Synchronization nhưng Java 5 java.util.concurrent.atomic cung cấp các lớp trình bao bọc cho int và long có thể được sử dụng để đạt được điều này về mặt nguyên tử mà không cần sử dụng Synchronization. Đi tới bài viết này để tìm hiểu thêm về atomic concurrent classes.
2. Lock Interface trong Java Concurrency API là gì? Nó có những lợi ích gì so với đồng bộ hóa?
Lock Interface cung cấp nhiều hoạt động khóa mở rộng hơn có thể đạt được bằng cách sử dụng các phương pháp và câu lệnh đồng bộ. Chúng cho phép cấu trúc linh hoạt hơn, có thể có các thuộc tính khá khác nhau và có thể hỗ trợ nhiều đối tượng Điều kiện được liên kết.
Ưu điểm của khóa là
👉 có thể làm cho chúng công bằng
👉 Có thể làm cho một chuỗi đáp ứng khi bị gián đoạn trong khi chờ đối tượng Lock.
👉 có thể cố gắng lấy lại khóa, nhưng hãy quay lại ngay lập tức hoặc sau một thời gian chờ nếu không thể lấy được khóa
👉 có thể lấy và phát hành các khóa trong các phạm vi khác nhau và theo các thứ tự khác nhau
Đọc thêm tại Java Lock Example.
3. Executor Framework là gì?
Trong Java 5, Executor Framework đã được giới thiệu với interface java.util.concurrent.Executor.
Executor Framework là một framework để chuẩn hóa việc gọi, lập lịch, thực thi và kiểm soát các tác vụ không đồng bộ theo một tập hợp các chính sách thực thi.
Tạo nhiều luồng không có giới hạn đến ngưỡng tối đa có thể khiến ứng dụng hết bộ nhớ heap. Vì vậy, tạo ThreadPool là một giải pháp tốt hơn vì một số lượng hữu hạn các chủ đề có thể được gộp lại và sử dụng lại. Khung thực thi tạo điều kiện thuận lợi cho quá trình tạo nhóm luồng trong java. Hãy xem bài đăng này để tìm hiểu với mã ví dụ để create thread pool using Executors framework.
4. BlockingQueue là gì? Làm thế nào chúng ta có thể thực hiện vấn đề Nhà sản xuất-Người tiêu dùng bằng cách sử dụng BlockingQueue?
java.util.concurrent.BlockingQueue là một Queue hỗ trợ các hoạt động đợi hàng đợi trở nên không trống khi truy xuất và xóa một phần tử, và đợi khoảng trống có sẵn trong hàng đợi khi thêm một phần tử.
BlockingQueue không chấp nhận giá trị null và ném NullPointerException nếu bạn cố gắng lưu trữ giá trị null trong hàng đợi.
Các triển khai BlockingQueue an toàn theo luồng. Tất cả các phương pháp xếp hàng đều có bản chất nguyên tử và sử dụng khóa bên trong hoặc các hình thức kiểm soát đồng thời khác.
Interface BlockingQueue là một phần của khuôn khổ bộ sưu tập java và nó chủ yếu được sử dụng để thực hiện vấn đề người tiêu dùng của nhà sản xuất. Kiểm tra bài đăng này để biết producer-consumer problem implementation using BlockingQueue.
5. Callable và Future là gì?
Java 5 đã giới thiệu interface java.util.concurrent.Callable trong gói đồng thời tương tự như interface Runnable nhưng nó có thể trả về bất kỳ Đối tượng nào và có thể ném Exception.
Interface có thể gọi sử dụng Generic để xác định kiểu trả về của Đối tượng. Lớp Executor cung cấp các phương thức hữu ích để thực thi Callable trong một nhóm luồng. Vì các tác vụ có thể gọi chạy song song, chúng ta phải đợi Đối tượng trả về. Các tác vụ có thể gọi trả về đối tượng java.util.concurrent.Future. Sử dụng Future, chúng ta có thể tìm ra trạng thái của nhiệm vụ Có thể gọi và lấy Đối tượng trả về. Nó cung cấp phương thức get() có thể đợi Callable kết thúc và sau đó trả về kết quả. Kiểm tra bài đăng này để biết Callable Future Example.
6. Lớp FutureTask là gì?
FutureTask là lớp triển khai cơ sở của Future interface và chúng ta có thể sử dụng nó với Executors để xử lý không đồng bộ. Hầu hết thời gian chúng ta không cần sử dụng lớp FutureTask nhưng nó thực sự hữu ích nếu chúng ta muốn ghi đè một số phương thức của interface Future và muốn giữ lại hầu hết việc triển khai cơ sở. Chúng ta chỉ có thể mở rộng lớp này và ghi đè các phương thức theo yêu cầu của chúng ta. Xem bài đăng Java FutureTask Example để tìm hiểu cách sử dụng nó và các phương pháp khác nhau mà nó có.
7. Các Lớp Concurrent Collection là gì?
Các lớp của Collection trong Java không nhanh, có nghĩa là nếu Collection sẽ bị thay đổi trong khi một số luồng đang duyệt qua nó bằng cách sử dụng trình lặp, thì iterator.next() sẽ ném ConcurrentModificationException.
Các lớp Concurrent Collection hỗ trợ đồng thời đầy đủ các truy xuất và đồng thời dự kiến có thể điều chỉnh cho các bản cập nhật.
Các lớp chính là ConcurrentHashMap, CopyOnWriteArrayList và
CopyOnWriteArraySet, hãy xem bài đăng này để tìm hiểu how to avoid ConcurrentModificationException when using iterator.
8. Executor Class là gì?
Lớp Executor cung cấp các phương thức tiện ích cho các lớp Executor: ExecutorService, SchedisedExecutorService, ThreadFactory và Callable.
Lớp Executor có thể được sử dụng để dễ dàng tạo Thread Pool trong java, đây cũng là lớp duy nhất hỗ trợ thực thi các triển khai Callable.
9. Một số cải tiến trong API Concurrency trong Java 8 là gì?
Một số cải tiến API đồng thời quan trọng là:
👉 ConcurrentHashMap các phương thức compute(), forEach(), forEachEntry(), forEachKey(), forEachValue(), merge(), Reduce() và search().
👉 CompletableFuture có thể được hoàn thành một cách rõ ràng (thiết lập giá trị và trạng thái của nó).
👉 Các nhà thực thi phương thức newWorkStealingPool() để tạo một nhóm luồng đánh cắp công việc bằng cách sử dụng tất cả các bộ xử lý có sẵn làm mức song song mục tiêu của nó.
Khuyến nghị đọc: Java 8 Features
Lớp Executor cung cấp các phương thức tiện ích cho các lớp Executor: ExecutorService, SchedisedExecutorService, ThreadFactory và Callable.
Lớp Executor có thể được sử dụng để dễ dàng tạo Thread Pool trong java, đây cũng là lớp duy nhất hỗ trợ thực thi các triển khai Callable.
9. Một số cải tiến trong API Concurrency trong Java 8 là gì?
Một số cải tiến API đồng thời quan trọng là:
👉 ConcurrentHashMap các phương thức compute(), forEach(), forEachEntry(), forEachKey(), forEachValue(), merge(), Reduce() và search().
👉 CompletableFuture có thể được hoàn thành một cách rõ ràng (thiết lập giá trị và trạng thái của nó).
👉 Các nhà thực thi phương thức newWorkStealingPool() để tạo một nhóm luồng đánh cắp công việc bằng cách sử dụng tất cả các bộ xử lý có sẵn làm mức song song mục tiêu của nó.
Khuyến nghị đọc: Java 8 Features
E. Xử lý ngoại lệ trong Java
1. Exception trong Java là gì?
Exception là một sự kiện lỗi có thể xảy ra trong quá trình thực thi một chương trình và làm gián đoạn luồng thông thường của nó. Exception có thể phát sinh từ các loại tình huống khác nhau như dữ liệu do người dùng nhập sai, lỗi phần cứng, lỗi kết nối mạng, v.v.
Bất cứ khi nào xảy ra bất kỳ lỗi nào trong khi thực hiện câu lệnh java, một đối tượng ngoại lệ được tạo và sau đó JRE cố gắng tìm trình xử lý ngoại lệ để xử lý ngoại lệ. Nếu tìm thấy trình xử lý ngoại lệ phù hợp thì đối tượng ngoại lệ được chuyển tới mã trình xử lý để xử lý ngoại lệ, được gọi là bắt ngoại lệ. Nếu không tìm thấy trình xử lý nào thì ứng dụng sẽ ném ngoại lệ vào môi trường thời gian chạy và JRE kết thúc chương trình.
Java Exception Framework chỉ được sử dụng để xử lý lỗi runtime, lỗi compiletime không được xử lý bởi Java Exception Framework.
2. Các Từ khoá Exception Handling trong Java là gì?
Có bốn từ khóa được sử dụng trong exception handling (xử lý ngoại lệ) java.
1. throw: Đôi khi chúng ta muốn tạo đối tượng ngoại lệ một cách rõ ràng và sau đó ném nó để tạm dừng quá trình xử lý bình thường của chương trình. từ khóa throw được sử dụng để ném ngoại lệ vào thời gian chạy để xử lý nó.
2. throws : Khi chúng ta ném bất kỳ ngoại lệ đã kiểm tra nào vào một phương thức và không xử lý nó, thì chúng ta cần sử dụng từ khóa throws trong chữ ký phương thức để cho chương trình người gọi biết các ngoại lệ có thể được đưa ra bởi phương thức. Phương thức trình gọi có thể xử lý các ngoại lệ này hoặc truyền nó sang phương thức trình gọi của nó bằng cách sử dụng từ khóa throws. Chúng ta có thể cung cấp nhiều ngoại lệ trong mệnh đề ném và nó cũng có thể được sử dụng với phương thức main() .
3. try-catch : Chúng tôi sử dụng khối try-catch để xử lý ngoại lệ trong mã của chúng tôi. try là phần bắt đầu của khối và catch là phần cuối của khối try để xử lý các trường hợp ngoại lệ. Chúng ta có thể có nhiều khối bắt với một khối try và khối trycatch cũng có thể được lồng vào nhau. khối catch yêu cầu một tham số phải thuộc loại Exception.
4. finally: finally là tùy chọn và chỉ có thể được sử dụng với khối try-catch. Vì ngoại lệ tạm dừng quá trình thực thi, chúng tôi có thể có một số tài nguyên đang mở mà sẽ không bị đóng, vì vậy chúng tôi có thể sử dụng khối cuối cùng. finally được thực thi luôn luôn, cho dù có xảy ra ngoại lệ hay không.
Có bốn từ khóa được sử dụng trong exception handling (xử lý ngoại lệ) java.
1. throw: Đôi khi chúng ta muốn tạo đối tượng ngoại lệ một cách rõ ràng và sau đó ném nó để tạm dừng quá trình xử lý bình thường của chương trình. từ khóa throw được sử dụng để ném ngoại lệ vào thời gian chạy để xử lý nó.
2. throws : Khi chúng ta ném bất kỳ ngoại lệ đã kiểm tra nào vào một phương thức và không xử lý nó, thì chúng ta cần sử dụng từ khóa throws trong chữ ký phương thức để cho chương trình người gọi biết các ngoại lệ có thể được đưa ra bởi phương thức. Phương thức trình gọi có thể xử lý các ngoại lệ này hoặc truyền nó sang phương thức trình gọi của nó bằng cách sử dụng từ khóa throws. Chúng ta có thể cung cấp nhiều ngoại lệ trong mệnh đề ném và nó cũng có thể được sử dụng với phương thức main() .
3. try-catch : Chúng tôi sử dụng khối try-catch để xử lý ngoại lệ trong mã của chúng tôi. try là phần bắt đầu của khối và catch là phần cuối của khối try để xử lý các trường hợp ngoại lệ. Chúng ta có thể có nhiều khối bắt với một khối try và khối trycatch cũng có thể được lồng vào nhau. khối catch yêu cầu một tham số phải thuộc loại Exception.
4. finally: finally là tùy chọn và chỉ có thể được sử dụng với khối try-catch. Vì ngoại lệ tạm dừng quá trình thực thi, chúng tôi có thể có một số tài nguyên đang mở mà sẽ không bị đóng, vì vậy chúng tôi có thể sử dụng khối cuối cùng. finally được thực thi luôn luôn, cho dù có xảy ra ngoại lệ hay không.
3. Giải thích cấu trúc phân cấp ngoại lệ của Java?
Các ngoại lệ trong Java là phân cấp và kế thừa được sử dụng để phân loại các loại ngoại lệ khác nhau. Throwable là lớp cha của Java Exceptions Hierarchy và nó có hai đối tượng con - Error và Exception. Các ngoại lệ được chia thành các ngoại lệ đã kiểm tra và ngoại lệ thời gian chạy.
Errors là các tình huống đặc biệt nằm ngoài phạm vi ứng dụng và không thể lường trước và khắc phục được, ví dụ như lỗi phần cứng, lỗi JVM hoặc lỗi hết bộ nhớ.
Checked Exceptions là các trường hợp đặc biệt mà chúng ta có thể lường trước được trong một chương trình và cố gắng khôi phục từ đó, ví dụ như FileNotFoundException. Chúng tôi nên nắm bắt ngoại lệ này và cung cấp thông báo hữu ích cho người dùng và ghi lại nó đúng cách cho mục đích gỡ lỗi. Ngoại lệ là lớp cha của tất cả các Checked Exceptions.
Runtime Exceptions là do lập trình không tốt, ví dụ như cố gắng truy xuất một phần tử từ Mảng. Chúng ta nên kiểm tra độ dài của mảng trước khi cố gắng truy xuất phần tử nếu không nó có thể ném ArrayIndexOutOfBoundException vào thời gian chạy. RuntimeException là lớp cha của tất cả các ngoại lệ thời gian chạy.
4. Các phương thức quan trọng của Java Exception Class là gì?
Exception và tất cả các lớp con của nó không cung cấp bất kỳ phương thức cụ thể nào và tất cả các phương thức đều được định nghĩa trong lớp cơ sở Throwable.
1. String getMessage() - Phương thức này trả về thông báo String of Throwable và thông báo có thể được cung cấp trong khi tạo ngoại lệ thông qua phương thức khởi tạo của nó.
2. String getLocalizedMessage() - Phương thức này được cung cấp để các lớp con có thể ghi đè nó để cung cấp thông báo địa phương cụ thể cho chương trình đang gọi. Việc triển khai lớp có thể ném của phương thức này chỉ cần sử dụng phương thức getMessage() để trả về thông báo ngoại lệ.
3. synchronized Throwable getCause() - Phương thức này trả về nguyên nhân của ngoại lệ hoặc id null mà nguyên nhân là không xác định.
4. String toString() - Phương thức này trả về thông tin về Throwable ở định dạng String, String trả về chứa tên của lớp Throwable và thông báo được bản địa hóa.
5. void printStackTrace() - Phương thức này in thông tin dấu vết ngăn xếp vào luồng lỗi chuẩn, phương thức này được nạp chồng và chúng ta có thể truyền PrintStream hoặc PrintWriter làm đối số để ghi thông tin dấu vết ngăn xếp vào tệp hoặc luồng.
5. Giải thích Tính năng Java 7 ARM và multi-catch block?
Nếu bạn đang tìm thấy nhiều ngoại lệ trong một khối thử duy nhất, bạn sẽ nhận thấy rằng mã khối catch trông rất xấu và chủ yếu bao gồm mã thừa để ghi lại lỗi, hãy ghi nhớ điều này Java 7 một trong những tính năng là khối multi-catch. nơi chúng ta có thể bắt nhiều ngoại lệ trong một khối catch duy nhất. Khối bắt với tính năng này trông giống như bên dưới:
catch(IOException | SQLException | Exception ex){ logger.error(ex); throw new MyException(ex.getMessage()); }
Hầu hết thời gian, chúng tôi sử dụng khối finally chỉ để đóng tài nguyên và đôi khi chúng tôi quên đóng chúng và nhận được ngoại lệ runtime khi tài nguyên cạn kiệt. Những ngoại lệ này rất khó gỡ lỗi và chúng tôi có thể cần phải xem xét từng nơi mà chúng tôi đang sử dụng loại tài nguyên đó để đảm bảo rằng chúng tôi đang đóng nó. Vì vậy, một trong những cải tiến của java 7 là try-withresources, nơi chúng ta có thể tạo một tài nguyên trong chính câu lệnh try và sử dụng nó bên trong khối try-catch. Khi việc thực thi ra khỏi khối try-catch, môi trường thời gian chạy sẽ tự động đóng các tài nguyên này. Mẫu khối try-catch với cải tiến này là:
try (MyResource mr = new MyResource())
{
System.out.println("MyResource
created in try-withresources");
} catch (Exception e) {
e.printStackTrace(); }
Đọc thêm về điều này tại Java 7 ARM.
6. Sự khác biệt giữa Checked và Unchecked Exception trong Java là gì?
1. Các ngoại lệ được kiểm tra nên được xử lý trong mã bằng cách sử dụng khối try-catch hoặc phương thức main() khác nên sử dụng từ khóa throws để cho JRE biết về các ngoại lệ này có thể bị ném khỏi chương trình. Các ngoại lệ không được chọn không bắt buộc phải được xử lý trong chương trình hoặc phải đề cập đến chúng trong mệnh đề ném.
2. Ngoại lệ là siêu lớp của tất cả các ngoại lệ đã được kiểm tra trong khi RuntimeException là siêu lớp của tất cả các ngoại lệ chưa được kiểm tra.
3. Các trường hợp ngoại lệ được kiểm tra là các trường hợp lỗi không phải do chương trình gây ra, ví dụ: FileNotFoundException khi đọc một tệp không hiện diện, trong khi các ngoại lệ không được kiểm tra chủ yếu do lập trình kém, ví dụ NullPointerException khi gọi một phương thức trên một tham chiếu đối tượng mà không chắc chắn rằng nó không rỗng.
7. Sự khác biệt giữa từ khóa throws và throw trong Java là gì?
Từ khóa throws được sử dụng với chữ ký của phương thức để khai báo các ngoại lệ mà phương thức có thể ném trong khi từ khóa throw được sử dụng để làm gián đoạn dòng chương trình và chuyển giao đối tượng ngoại lệ cho thời gian chạy để xử lý nó.
8. Làm thế nào để viết ngoại lệ tùy chỉnh trong Java?
Chúng tôi có thể mở rộng lớp Ngoại lệ hoặc bất kỳ lớp con nào của nó để tạo lớp ngoại lệ tùy chỉnh của chúng tôi. Lớp ngoại lệ tùy chỉnh có thể có các biến và phương thức riêng mà chúng ta có thể sử dụng để chuyển mã lỗi hoặc thông tin liên quan đến ngoại lệ khác tới trình xử lý ngoại lệ.
Dưới đây là một ví dụ đơn giản về ngoại lệ tùy chỉnh.
package com.journaldev.exceptions;
import java.io.IOException;
public class MyException extends IOException {
private static final long serialVersionUID = 4664456874499611218L;
private String errorCode="Unknown_Exception";
public MyException(String
message, String errorCode){ super(message); this.errorCode=errorCode;
}
public String getErrorCode(){ return this.errorCode;
} }
OutOfMemoryError trong Java là một lớp con của java.lang.VirtualMachineError và nó bị JVM ném ra khi hết bộ nhớ heap. Chúng tôi có thể khắc phục lỗi này bằng cách cung cấp thêm bộ nhớ để chạy ứng dụng java thông qua các tùy chọn java.
$> java MyProgram -Xms1024m -Xmx1024m -XX : PermSize = 64M XX: MaxPermSize = 256m
10. Các tình huống khác nhau gây ra “Exception in thread main” là gì?
Một số trường hợp ngoại lệ chuỗi chính phổ biến là:
👉 Exception in thread main java.lang.UnsupportedClassVersionError : Ngoại lệ này xảy ra khi lớp java của bạn được biên dịch từ một phiên bản JDK khác và bạn đang cố gắng chạy nó từ một phiên bản java khác.
👉 Exception in thread main java.lang.NoClassDefFoundError : Có hai biến thể của ngoại lệ này. Đầu tiên là nơi bạn cung cấp tên đầy đủ của lớp với phần mở rộng .class. Kịch bản thứ hai là khi Class không được tìm thấy.
👉 Exception in thread main java.lang.NoSuchMethodError: main : Ngoại lệ này xảy ra khi bạn đang cố gắng chạy một lớp không có phương thức chính.
👉 Exception in thread main java.lang.ArithmeticException : Bất cứ khi nào bất kỳ ngoại lệ nào được ném ra từ phương thức main, nó sẽ in ra ngoại lệ là interface điều khiển. Phần đầu tiên giải thích rằng ngoại lệ được ném ra từ phương thức main, phần thứ hai in tên lớp ngoại lệ và sau dấu hai chấm, nó in ra thông báo ngoại lệ.
Đọc thêm về những điều này tại Java Exception in Thread main.
11. Sự khác biệt giữa final, finally and finalize trong Java là gì?
Final and finally là các từ khóa trong java trong khi finalize là một phương thức. Từ khóa final có thể được sử dụng với các biến lớp để chúng không thể được gán lại, với lớp để tránh mở rộng bởi các lớp và với các phương thức để tránh ghi đè bởi các lớp con, từ khóa finally được sử dụng với khối try-catch để cung cấp các câu lệnh sẽ luôn được thực thi ngay cả khi một số ngoại lệ phát sinh, finally thường được sử dụng để đóng tài nguyên. Phương thức finalize() được thực thi bởi Garbage Collector trước khi đối tượng bị phá hủy, đó là một cách tuyệt vời để đảm bảo rằng tất cả các tài nguyên chung đã được đóng lại.
Trong số ba, chỉ finally có liên quan đến xử lý ngoại lệ java.
12. Điều gì xảy ra khi ngoại lệ được ném bởi phương thức main?
Khi ngoại lệ được ném bởi phương thức main(), Java Runtime sẽ kết thúc chương trình và in thông báo ngoại lệ và dấu vết ngăn xếp trong bảng điều khiển hệ thống.
13. Chúng ta có thể có một khối catch trống không?
Chúng ta có thể có một khối catch trống nhưng đó là ví dụ về lập trình tồi tệ nhất. Chúng ta không bao giờ nên có khối catch rỗng bởi vì nếu khối đó bị bắt giữ ngoại lệ, chúng ta sẽ không có thông tin về ngoại lệ và việc gỡ lỗi nó sẽ là một cơn ác mộng. Cần có ít nhất một câu lệnh ghi nhật ký để ghi chi tiết ngoại lệ trong bảng điều khiển hoặc tệp nhật ký.
14. Cung cấp một số phương pháp hay nhất về xử lý ngoại lệ trong Java?
Một số phương pháp hay nhất liên quan đến Xử lý ngoại lệ trong Java là:
👉 Sử dụng các Ngoại lệ Cụ thể để dễ gỡ lỗi.
👉 Bỏ các Ngoại lệ Early (Fail-Fast) trong chương trình.
👉 Bắt ngoại lệ muộn trong chương trình, để người gọi xử lý ngoại lệ.
👉 Sử dụng tính năng Java 7 ARM để đảm bảo tài nguyên được đóng hoặc sử dụng khối cuối cùng để đóng chúng đúng cách.
👉 Luôn ghi nhật ký các thông báo ngoại lệ cho mục đích gỡ lỗi.
👉 Sử dụng khối multi-catch để đóng sạch hơn.
👉 Sử dụng các ngoại lệ tùy chỉnh để ném một loại ngoại lệ duy nhất khỏi API ứng dụng của bạn.
👉 Tuân theo quy ước đặt tên, luôn kết thúc bằng Ngoại lệ.
👉 Ghi lại các Ngoại lệ được Ném bằng một phương pháp sử dụng @throws trong javadoc.
👉 Các trường hợp ngoại lệ rất tốn kém, vì vậy hãy ném nó chỉ khi nó có ý nghĩa. Nếu không, bạn có thể bắt chúng và cung cấp phản hồi rỗng hoặc trống.
Đọc thêm chi tiết về chúng tại Java Exception Handling Best Practices.
15. Sự cố với các chương trình dưới đây là gì và chúng ta khắc phục nó như thế nào?
Trong phần này, chúng ta sẽ xem xét một số câu hỏi lập trình liên quan đến các ngoại lệ của java.
a. Vấn đề với chương trình dưới đây là gì?
package com.journaldev.exceptions;
import java.io.FileNotFoundException; import java.io.IOException;
public class TestException {
public static void main(String[] args) { try { testExceptions();
} catch (FileNotFoundException | IOException e) {
e.printStackTrace();
}
}
public static void testExceptions() throws IOException, FileNotFoundException{
}
}
Chương trình trên sẽ không biên dịch và bạn sẽ nhận được thông báo lỗi là “Ngoại lệ FileNotFoundException đã bị IOException thay thế bắt giữ”. Điều này là do FileNotFoundException là lớp con của IOException, có hai cách để giải quyết vấn đề này. Cách đầu tiên là sử dụng khối bắt đơn cho cả hai trường hợp ngoại lệ.
try { testExceptions(); }catch(FileNotFoundException e){ e.printStackTrace(); }catch (IOException e) {
e.printStackTrace();
} Một cách khác là loại bỏ FileNotFoundException khỏi khối đa bắt. try { testExceptions(); }catch (IOException e) {
e.printStackTrace(); }Bạn có thể chọn bất kỳ phương pháp nào trong số này dựa trên mã khối bắt của bạn.
1. Vấn đề với chương trình dưới đây là gì?
package com.journaldev.exceptions;
import java.io.FileNotFoundException; import java.io.IOException;
import javax.xml.bind.JAXBException;
public class TestException1 {
public static void main(String[] args) { try { go();
} catch (IOException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace(); } catch (JAXBException e) {
e.printStackTrace();
}
}
public static void go() throws IOException, JAXBException,
FileNotFoundException{
}
}
Chương trình sẽ không biên dịch vì FileNotFoundException là lớp con của IOException, vì vậy khối bắt của FileNotFoundException không thể truy cập được và bạn sẽ nhận được thông báo lỗi là “Khối bắt không thể truy cập cho FileNotFoundException. Nó đã được xử lý bởi khối catch cho IOException ”.
Bạn cần sửa thứ tự khối Catch để giải quyết vấn đề này.
try { go();
} catch (FileNotFoundException e) {
e.printStackTrace(); } catch (IOException e) {
e.printStackTrace(); } catch (JAXBException e) {
e.printStackTrace(); }
Chú ý rằng JAXBException là không liên quan đến IOException hoặc FileNotFoundException và có thể được đặt ở bất cứ đâu trong hệ thống phân cấp khối catch trên.
2. Vấn đề với chương trình dưới đây là gì?
package com.journaldev.exceptions;
import java.io.IOException;
import javax.xml.bind.JAXBException;
public class TestException2 {
public static void main(String[] args) {
public static void main(String[] args) {
try { foo();
} catch (IOException e) {
e.printStackTrace(); }catch(JAXBException e){ e.printStackTrace(); }catch(NullPointerException e){
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}
}
public static void foo() throws IOException{
}
}
Chương trình sẽ không biên dịch vì JAXBException là một ngoại lệ đã được kiểm tra và phương thức foo ( ) sẽ ném ngoại lệ này để bắt trong phương thức gọi. Bạn sẽ nhận được thông báo lỗi là “Khối bắt không thể truy cập cho JAXBException. Ngoại lệ này không bao giờ được ném ra khỏi nội dung câu lệnh try ”.
Để giải quyết vấn đề này, bạn sẽ phải loại bỏ khối bắt của JAXBException.
Lưu ý rằng việc bắt NullPointerException là hợp lệ vì nó là một ngoại lệ không được kiểm tra.
3. Vấn đề với chương trình dưới đây là gì?
package com.journaldev.exceptions;
import java.io.IOException;
import javax.xml.bind.JAXBException;
public class TestException2 {
public static void main(String[] args) { try { foo();
} catch (IOException e) {
e.printStackTrace(); }catch(JAXBException e){ e.printStackTrace(); }catch(NullPointerException e){
e.printStackTrace(); }catch(Exception e){
e.printStackTrace();
}}
public static void foo() throws IOException{
}}
Đây là một câu hỏi mẹo, không có vấn đề gì với mã và nó sẽ biên dịch thành công. Chúng ta luôn có thể bắt Exception hoặc bất kỳ ngoại lệ nào chưa được kiểm tra ngay cả khi nó không nằm trong mệnh đề ném của phương thức.
Tương tự nếu một phương thức (foo) khai báo ngoại lệ không được kiểm tra trong mệnh đề ném, thì không bắt buộc phải xử lý điều đó trong chương trình.
} catch (IOException e) {
e.printStackTrace(); }catch(JAXBException e){ e.printStackTrace(); }catch(NullPointerException e){
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}
}
public static void foo() throws IOException{
}
}
Chương trình sẽ không biên dịch vì JAXBException là một ngoại lệ đã được kiểm tra và phương thức foo ( ) sẽ ném ngoại lệ này để bắt trong phương thức gọi. Bạn sẽ nhận được thông báo lỗi là “Khối bắt không thể truy cập cho JAXBException. Ngoại lệ này không bao giờ được ném ra khỏi nội dung câu lệnh try ”.
Để giải quyết vấn đề này, bạn sẽ phải loại bỏ khối bắt của JAXBException.
Lưu ý rằng việc bắt NullPointerException là hợp lệ vì nó là một ngoại lệ không được kiểm tra.
3. Vấn đề với chương trình dưới đây là gì?
package com.journaldev.exceptions;
import java.io.IOException;
import javax.xml.bind.JAXBException;
public class TestException2 {
public static void main(String[] args) { try { foo();
} catch (IOException e) {
e.printStackTrace(); }catch(JAXBException e){ e.printStackTrace(); }catch(NullPointerException e){
e.printStackTrace(); }catch(Exception e){
e.printStackTrace();
}}
public static void foo() throws IOException{
}}
Đây là một câu hỏi mẹo, không có vấn đề gì với mã và nó sẽ biên dịch thành công. Chúng ta luôn có thể bắt Exception hoặc bất kỳ ngoại lệ nào chưa được kiểm tra ngay cả khi nó không nằm trong mệnh đề ném của phương thức.
Tương tự nếu một phương thức (foo) khai báo ngoại lệ không được kiểm tra trong mệnh đề ném, thì không bắt buộc phải xử lý điều đó trong chương trình.
4. Vấn đề với chương trình dưới đây là gì?
package com.journaldev.exceptions;
public class TestException3 {
public static void main(String[] args) { try{ bar();
}catch(NullPointerException e){
e.printStackTrace(); }catch(Exception e){
e.printStackTrace();
} foo();
}
public static void bar(){
}
public static void foo() throws NullPointerException{
}
}
Chương trình trên sẽ không biên dịch vì chữ ký của phương thức start ( ) không giống nhau trong lớp con. Để khắc phục sự cố này, chúng ta có thể thay đổi chữ ký phương thức trong lớp con giống hoàn toàn với lớp cha hoặc chúng ta có thể loại bỏ mệnh đề throws khỏi phương thức lớp con như được hiển thị bên dưới.
5. Vấn đề với chương trình dưới đây là gì?
package com.journaldev.exceptions;
import java.io.IOException;
public class TestException4 {
public void start() throws IOException{
}
public void foo() throws NullPointerException{
}
}
class TestException5 extends TestException4{
public void start() throws Exception{
}
public void foo() throws RuntimeException{
}
}
Chương trình trên sẽ không biên dịch vì đối tượng ngoại lệ trong khối multi-catch là đối tượng cuối cùng và chúng tôi không thể thay đổi giá trị của nó. Bạn sẽ gặp lỗi thời gian biên dịch vì “Không thể gán tham số e của khối đa bắt”.
Chúng ta phải loại bỏ việc gán “e” cho đối tượng ngoại lệ mới để giải quyết lỗi này. Đọc thêm tại Java 7 multi-catch block.
Bài viết được dịch lại từ journaldev.com.
Cảm ơn bạn đã xem bài viết!
Nếu có bất cứ lỗi sai nào trong bài bạn có thể góp ý cho chúng tôi bằng cách bình luận bên dưới.

0 Nhận xét