CFSDN nhấn mạnh vào giá trị tạo ra nguồn mở và chúng tôi cam kết xây dựng nền tảng chia sẻ tài nguyên để mọi nhân viên CNTT có thể tìm thấy thế giới tuyệt vời của bạn tại đây.
Bài viết trên blog CFSDN về Câu hỏi và trả lời phỏng vấn cơ bản về đa luồng và đồng thời JAVA (Bản dịch) này được tác giả sưu tầm và biên soạn. Nếu các bạn quan tâm đến bài viết này thì nhớ like nhé.

Câu hỏi phỏng vấn đa luồng Java.
1. Sự khác biệt giữa tiến trình và luồng là gì?
Một tiến trình là một môi trường chạy độc lập, có thể được coi là một chương trình hoặc một ứng dụng. Một luồng là một tác vụ được thực thi trong một tiến trình. Môi trường thời gian chạy Java là một tiến trình đơn lẻ chứa các lớp và chương trình khác nhau. Chủ đề có thể được gọi là quá trình nhẹ. Các luồng yêu cầu ít tài nguyên hơn để tạo và cư trú trong một quy trình và có thể chia sẻ tài nguyên trong quy trình đó.
2. Lợi ích của lập trình đa luồng là gì?
Trong một chương trình đa luồng, nhiều luồng được thực thi đồng thời để nâng cao hiệu quả của chương trình. CPU sẽ không chuyển sang trạng thái không hoạt động vì một luồng cần chờ tài nguyên. Nhiều luồng chia sẻ bộ nhớ heap, vì vậy tốt hơn là tạo nhiều luồng để thực hiện một số tác vụ hơn là tạo nhiều tiến trình. Ví dụ: Servlets tốt hơn CGI vì Servlets hỗ trợ đa luồng trong khi CGI thì không.
3. Sự khác biệt giữa luồng người dùng và luồng daemon là gì?
Khi chúng ta tạo một luồng trong chương trình Java, nó được gọi là luồng người dùng. Một luồng daemon là một luồng thực thi ở chế độ nền và không ngăn JVM chấm dứt. Khi không có luồng người dùng nào đang chạy, JVM sẽ đóng chương trình và thoát. Các luồng con được tạo bởi một luồng daemon vẫn là các luồng daemon.
4. Làm thế nào để tạo một chủ đề?
Có hai cách để tạo một luồng: một là triển khai giao diện Runnable, sau đó chuyển nó đến hàm tạo Thread để tạo một đối tượng Thread; hai là kế thừa trực tiếp lớp Thread. Nếu bạn muốn biết thêm, bạn có thể đọc bài viết này về cách tạo luồng trong Java.
5. Vòng đời của thread khác nhau như thế nào?
Khi chúng ta tạo một luồng mới trong chương trình Java, trạng thái của nó là Mới. Khi chúng ta gọi phương thức start() của thread, trạng thái sẽ được thay đổi thành Runnable. Bộ lập lịch luồng phân bổ thời gian CPU cho các luồng trong nhóm luồng có thể chạy được và thay đổi trạng thái của chúng thành Đang chạy. Các trạng thái luồng khác bao gồm Đang chờ, Bị chặn và Đã chết. Đọc bài viết này để tìm hiểu thêm về vòng đời của luồng.
6. Tôi có thể gọi trực tiếp phương thức run() của lớp Thread không?
Tất nhiên, nhưng nếu chúng ta gọi phương thức run() của Thread, nó sẽ hoạt động giống như một phương thức bình thường. Để thực thi mã của chúng ta trong một luồng mới, chúng ta phải sử dụng phương thức Thread.start().
7. Làm thế nào để tạm dừng một luồng đang chạy trong một khoảng thời gian?
Chúng ta có thể sử dụng phương thức Sleep() của lớp Thread để tạm dừng luồng trong một khoảng thời gian. Cần lưu ý rằng điều này không chấm dứt luồng. Khi luồng được đánh thức khỏi chế độ ngủ, trạng thái của luồng sẽ được thay đổi thành Runnable và nó sẽ được thực thi theo lịch trình của luồng.
8. Bạn hiểu gì về mức độ ưu tiên của luồng?
Mỗi luồng có một mức độ ưu tiên Nói chung, các luồng có mức độ ưu tiên cao sẽ được ưu tiên khi chạy, nhưng điều này phụ thuộc vào việc triển khai lập lịch luồng, điều này phụ thuộc vào hệ điều hành. Chúng ta có thể xác định mức độ ưu tiên của các luồng, nhưng điều này không đảm bảo rằng các luồng có mức độ ưu tiên cao sẽ thực thi trước các luồng có mức độ ưu tiên thấp. Mức độ ưu tiên của luồng là một biến int (từ 1-10), với 1 đại diện cho mức độ ưu tiên thấp nhất và 10 đại diện cho mức độ ưu tiên cao nhất.
9. Lập lịch luồng và Cắt thời gian là gì?
Bộ lập lịch luồng là một dịch vụ hệ điều hành chịu trách nhiệm phân bổ thời gian CPU cho các luồng ở trạng thái Runnable. Khi chúng ta tạo một luồng và khởi động nó, việc thực thi nó phụ thuộc vào việc triển khai bộ lập lịch luồng. Cắt thời gian đề cập đến quá trình phân bổ thời gian CPU có sẵn cho các luồng Runnable có sẵn. Việc phân bổ thời gian CPU có thể dựa trên mức độ ưu tiên của luồng hoặc thời gian chờ đợi của luồng. Lập lịch luồng không được kiểm soát bởi máy ảo Java, vì vậy tốt hơn là ứng dụng nên kiểm soát nó (nghĩa là không làm cho chương trình của bạn phụ thuộc vào mức độ ưu tiên của luồng).
10. Trong đa luồng, chuyển ngữ cảnh là gì?
Chuyển ngữ cảnh là quá trình lưu trữ và khôi phục trạng thái CPU, cho phép thực thi luồng tiếp tục thực thi từ điểm bị gián đoạn. Chuyển ngữ cảnh là một tính năng thiết yếu của hệ điều hành đa nhiệm và môi trường đa luồng.
11. Làm thế nào để bạn đảm bảo rằng luồng chứa phương thức 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 Joint() của lớp Thread để đảm bảo rằng tất cả các luồng được tạo bởi chương trình kết thúc trước khi phương thức main() thoát. Đây là bài viết về phương thức Joint() của lớp Thread.
12. Các thread giao tiếp với nhau như thế nào?
Khi tài nguyên có thể được chia sẻ giữa các luồng, giao tiếp giữa các luồng là một phương tiện quan trọng để điều phối chúng. Phương thức wait()\notify()\notifyAll() trong lớp Object có thể được sử dụng để liên lạc giữa các luồng về trạng thái khóa tài nguyên. Bấm vào đây để tìm hiểu thêm về chủ đề chờ, thông báo và thông báo Tất cả.
13.Tại sao các phương thức giao tiếp thread wait(), notification() và notificationAll() được định nghĩa trong lớp Object?
Mỗi đối tượng trong Java có một khóa (màn hình, cũng có thể là một màn hình) và các phương thức như wait() và notification() được sử dụng để chờ khóa của đối tượng hoặc thông báo cho các luồng khác rằng màn hình của đối tượng có sẵn. Không có khóa hoặc bộ đồng bộ hóa nào có sẵn cho bất kỳ đối tượng nào trong các luồng Java. Đó là lý do tại sao các phương thức này là một phần của lớp Object để mọi lớp trong Java đều có các phương thức cơ bản để giao tiếp giữa các luồng.
14. Tại sao phải gọi wait(), notification() và notificationAll() trong các phương thức được đồng bộ hóa hoặc các khối được đồng bộ hóa?
Khi một thread cần gọi phương thức wait() của một đối tượng thì thread đó phải sở hữu khóa của đối tượng đó, sau đó nó sẽ nhả khóa đối tượng và vào trạng thái chờ cho đến khi các thread khác gọi phương thức notification() trên đối tượng đó. Tương tự, khi một thread cần gọi phương thức notification() của đối tượng, nó sẽ giải phóng khóa của đối tượng để các thread đang chờ khác có thể lấy được khóa đối tượng. Vì tất cả các phương thức này đều yêu cầu luồng giữ khóa của đối tượng, điều này chỉ có thể đạt được thông qua đồng bộ hóa, nên chúng chỉ có thể được gọi trong các phương thức được đồng bộ hóa hoặc các khối được đồng bộ hóa.
15. Tại sao các phương thức sleep() và energy() của lớp Thread lại tĩnh?
Các phương thức sleep() và năng suất() của lớp Thread sẽ chạy trên luồng hiện đang thực thi. Vì vậy, sẽ không có ý nghĩa gì khi gọi các phương thức này trên các luồng khác đang chờ. Đó là lý do tại sao các phương pháp này là tĩnh. Chúng có thể hoạt động trong luồng hiện đang thực thi và tránh cho người lập trình nhầm tưởng rằng các phương thức này có thể được gọi trong các luồng không chạy khác.
16. Làm thế nào để đảm bảo an toàn cho sợi chỉ?
Có nhiều cách để đảm bảo an toàn luồng trong Java - đồng bộ hóa, sử dụng các lớp đồng thời nguyên tử, triển khai các khóa đồng thời, sử dụng từ khóa dễ bay hơi, sử dụng các lớp bất biến và an toàn luồng. Bạn có thể tìm hiểu thêm trong hướng dẫn về an toàn luồng.
17. Vai trò của từ khóa dễ bay hơi trong Java là gì?
Khi chúng ta sử dụng từ khóa dễ bay hơi để sửa đổi một biến, luồng sẽ đọc biến đó trực tiếp và không lưu vào bộ đệm. Điều này đảm bảo rằng các biến được đọc bởi luồng nhất quán với các biến trong bộ nhớ.
18. Lựa chọn nào tốt hơn, phương pháp đồng bộ hóa hay khối được đồng bộ hóa?
Khối được đồng bộ hóa là lựa chọn tốt hơn vì nó không khóa toàn bộ đối tượng (tất nhiên bạn cũng có thể làm cho nó khóa toàn bộ đối tượng). Các phương thức được đồng bộ hóa sẽ khóa toàn bộ đối tượng, ngay cả khi có nhiều khối được đồng bộ hóa không liên quan trong lớp, điều này thường khiến chúng ngừng thực thi và cần phải đợi để lấy được khóa trên đối tượng.
19. Làm thế nào để tạo một chuỗi daemon?
Luồng này có thể được đặt làm luồng daemon bằng phương thức setDaemon(true) của lớp Thread. Cần lưu ý rằng phương thức này cần được gọi trước khi gọi phương thức start(), nếu không IllegalThreadStateException sẽ được ném ra.
20. ThreadLocal là gì?
ThreadLocal được sử dụng để tạo các biến cục bộ của luồng. Chúng tôi biết rằng tất cả các luồng của một đối tượng sẽ chia sẻ các biến toàn cục của nó, vì vậy các biến này không an toàn cho luồng. Chúng tôi có thể sử dụng công nghệ đồng bộ hóa. Nhưng khi không muốn sử dụng tính năng đồng bộ hóa, chúng ta có thể chọn biến ThreadLocal.
Mỗi thread sẽ có các biến Thread riêng và chúng có thể sử dụng phương thức get()\set() để lấy các giá trị mặc định hoặc thay đổi giá trị của chúng trong thread. Các phiên bản ThreadLocal thường muốn trạng thái luồng liên quan của chúng là thuộc tính tĩnh riêng tư. Trong bài viết Ví dụ về ThreadLocal bạn có thể thấy một chương trình nhỏ về ThreadLocal.
21. Nhóm chủ đề là gì? Tại sao nó được khuyến khích sử dụng nó?
ThreadGroup là một lớp có mục đích cung cấp thông tin về một nhóm luồng.
API ThreadGroup tương đối yếu và không cung cấp nhiều chức năng hơn Thread. Nó có hai chức năng chính: một là lấy danh sách các luồng đang hoạt động trong nhóm luồng; hai là thiết lập trình xử lý ngoại lệ chưa được nắm bắt (trình xử lý ngoại lệ không được bắt) cho luồng. Tuy nhiên, trong Java 1.5, lớp Thread cũng đã thêm phương thức setUncaughtExceptionHandler(UncaughtExceptionHandler eh) nên ThreadGroup đã lỗi thời và không được khuyến khích tiếp tục sử dụng.
?
1
2
3
4
5
6
7
8
9
|
t1.setUncaughtExceptionHandler(
mới
Trình xử lý ngoại lệ không bắt được(){
@Ghi đè
công cộng
vô hiệu
uncaughtException(Luồng t, Có thể ném e) {
Hệ thống.out.println(
"đã xảy ra ngoại lệ:"
+e.getMessage());
}
});
|
22. Java thread dump (Thread Dump) là gì và làm cách nào để có được nó?
Kết xuất luồng là danh sách các luồng hoạt động JVM, rất hữu ích để phân tích các tắc nghẽn và bế tắc của hệ thống. Có nhiều cách để lấy kết xuất luồng - sử dụng lệnh Profiler, Kill -3, công cụ jstack, v.v. Tôi thích công cụ jstack hơn vì nó dễ sử dụng và đi kèm với JDK. Vì nó là một công cụ dựa trên thiết bị đầu cuối nên chúng tôi có thể viết một số tập lệnh để tạo các kết xuất luồng định kỳ để phân tích. Đọc tài liệu này để tìm hiểu thêm về cách tạo kết xuất luồng.
23. Bế tắc là gì? Làm thế nào để phân tích và tránh bế tắc?
Bế tắc đề cập đến tình huống có nhiều hơn hai luồng bị chặn vĩnh viễn. Tình huống này yêu cầu ít nhất hai luồng nữa và nhiều hơn hai tài nguyên.
Để phân tích sự bế tắc, chúng ta cần xem xét kết xuất luồng của ứng dụng Java. Chúng ta cần tìm ra chủ đề nào đang ở trạng thái BLOCKED và tài nguyên mà chúng đang chờ đợi. Mỗi tài nguyên có một id duy nhất, sử dụng id này chúng ta có thể tìm ra luồng nào đã sở hữu khóa đối tượng của nó.
Tránh khóa lồng nhau, chỉ sử dụng khóa khi cần thiết và tránh chờ đợi vô thời hạn là những cách phổ biến để tránh bế tắc. Hãy đọc bài viết này để tìm hiểu cách phân tích khóa chết.
24. Lớp Java Time là gì? Làm cách nào để tạo một nhiệm vụ với khoảng thời gian cụ thể?
java.util.Timer là một lớp công cụ 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 cụ thể trong tương lai. Lớp Hẹn giờ có thể được sử dụng để lên lịch các tác vụ một lần hoặc các tác vụ định kỳ.
java.util.TimerTask là một lớp trừu tượng triển khai giao diện Runnable. Chúng ta cần kế thừa lớp này để tạo các tác vụ theo lịch trình của riêng mình và sử dụng Bộ hẹn giờ để lên lịch thực hiện nó.
Dưới đây là ví dụ về java Hẹn giờ.
25. Nhóm chủ đề là gì? Làm cách nào để tạo nhóm luồng Java?
Nhóm luồng quản lý một nhóm các luồng công việc và cũng bao gồm một hàng đợi để đặt các tác vụ đang chờ được thực thi.
java.util.concurrent.Executors cung cấp cách triển khai giao diện java.util.concurrent.Executor để tạo nhóm luồng. Ví dụ về Nhóm luồng cho biết cách tạo và sử dụng nhóm luồng hoặc đọc ví dụ về ScheduledThreadPoolExecutor để tìm hiểu cách tạo một tác vụ định kỳ.
Câu hỏi phỏng vấn đồng thời Java.
1. Hoạt động nguyên tử là gì? Các lớp nguyên tử trong API đồng thời Java là gì?
Một hoạt động nguyên tử đề cập đến một đơn vị nhiệm vụ hoạt động không bị ảnh hưởng bởi các hoạt động khác. Hoạt động nguyên tử là một phương tiện cần thiết để tránh sự không nhất quán của dữ liệu trong môi trường đa luồng.
int++ không phải là một hoạt động nguyên tử, vì vậy khi một luồng đọc giá trị của nó và thêm 1, một luồng khác có thể đọc giá trị trước đó, điều này sẽ gây ra lỗi.
Để giải quyết vấn đề này, chúng tôi phải đảm bảo rằng hoạt động tăng là nguyên tử. Trước JDK1.5, chúng tôi có thể sử dụng công nghệ đồng bộ hóa để thực hiện việc này. Kể từ JDK 1.5, gói java.util.concurrent.atomic cung cấp các lớp tải kiểu int và long tự động đảm bảo rằng các hoạt động của chúng là nguyên tử và không yêu cầu sử dụng đồng bộ hóa. Bạn có thể đọc bài viết này để tìm hiểu về các lớp nguyên tử của Java.
2. Giao diện Lock trong Java Concurrency API là gì? Ưu điểm của nó so với đồng bộ hóa là gì?
Giao diện Lock cung cấp nhiều hoạt động khóa có khả năng mở rộng hơn so với các phương thức được đồng bộ hóa và các khối được đồng bộ hóa. Chúng cho phép các cấu trúc linh hoạt hơn có thể có các thuộc tính hoàn toàn khác nhau và có thể hỗ trợ nhiều lớp đối tượng có điều kiện liên quan.
Ưu điểm của nó là:
- Có thể làm cho ổ khóa công bằng hơn
- Bạn có thể kích hoạt các luồng để phản hồi các ngắt trong khi chờ khóa
- Bạn có thể để chuỗi cố gắng lấy khóa và quay lại ngay lập tức hoặc đợi trong một khoảng thời gian khi không thể lấy được khóa.
- Khóa có thể được lấy và mở ở các phạm vi khác nhau và theo thứ tự khác nhau
Đọc thêm về các ví dụ về khóa.
3. Khung Executor là gì?
Khung Executor được giới thiệu trong Java 5 với giao diện java.util.concurrent.Executor. Khung Executor là một khung cho các tác vụ không đồng bộ được gọi, lên lịch, thực thi và kiểm soát theo một tập hợp các chiến lược thực thi.
Việc tạo luồng không giới hạn có thể khiến bộ nhớ ứng dụng bị tràn. Vì vậy, tạo nhóm luồng là giải pháp tốt hơn vì số lượng luồng có thể bị giới hạn và những luồng này có thể được tái chế và tái sử dụng. Rất thuận tiện khi tạo nhóm luồng bằng khung Executor. Đọc bài viết này để tìm hiểu cách tạo nhóm luồng bằng khung Executor.
4. Hàng đợi chặn là gì? Làm cách nào để sử dụng hàng đợi chặn để triển khai mô hình nhà sản xuất-người tiêu dùng?
Đặc điểm của java.util.concurrent.BlockingQueue là: khi hàng đợi trống, thao tác lấy hoặc xóa phần tử khỏi hàng đợi sẽ bị chặn hoặc khi hàng đợi đầy, thao tác thêm phần tử vào hàng đợi sẽ bị chặn .
Hàng đợi chặn không chấp nhận giá trị null. Khi bạn cố gắng thêm giá trị null vào hàng đợi, nó sẽ ném ra một ngoại lệ NullPointerException.
Việc triển khai hàng đợi chặn là an toàn theo luồng và tất cả các phương thức truy vấn đều là nguyên tử và sử dụng các khóa bên trong hoặc các hình thức kiểm soát đồng thời khác.
Giao diện BlockingQueue là một phần của khung bộ sưu tập java và chủ yếu được sử dụng để triển khai vấn đề nhà sản xuất-người tiêu dùng.
Đọc bài viết này để tìm hiểu cách triển khai vấn đề nhà sản xuất-người tiêu dùng bằng cách chặn hàng đợi.
5. Có thể gọi được và Tương lai là gì?
Java 5 đã giới thiệu giao diện java.util.concurrent.Callable trong gói concurrency, rất giống với giao diện Runnable, nhưng nó có thể trả về một đối tượng hoặc ném ra một ngoại lệ.
Giao diện Callable sử dụng generics để xác định kiểu trả về của nó. Lớp Executors cung cấp một số phương thức hữu ích để thực thi các tác vụ trong Callables trong nhóm luồng. Vì tác vụ có thể gọi là song song nên chúng ta phải đợi kết quả mà nó trả về. Đối tượng java.util.concurrent.Future giải quyết vấn đề này cho chúng ta. Sau khi thread pool submit task Callable, một đối tượng Future được trả về. Sử dụng nó, chúng ta có thể biết trạng thái của task Callable và nhận được kết quả thực thi mà Callable trả về. Future cung cấp phương thức get() để chúng ta có thể đợi Callable kết thúc và nhận kết quả thực thi của nó.
Đọc bài viết này để biết thêm ví dụ về Callable, Future.
6. FutureTask là gì?
FutureTask là một triển khai cơ bản của Future mà chúng ta có thể sử dụng với Executor để xử lý các tác vụ không đồng bộ. Thông thường chúng ta không cần sử dụng lớp FutureTask, nhưng nó trở nên rất hữu ích khi chúng ta dự định ghi đè một số phương thức của giao diện Future và giữ nguyên cách triển khai cơ bản ban đầu. Chúng ta chỉ có thể kế thừa từ nó và ghi đè các phương thức chúng ta cần. Đọc ví dụ Java FutureTask để tìm hiểu cách sử dụng nó.
7. Việc triển khai các container đồng thời là gì?
Các lớp bộ sưu tập Java không nhanh, có nghĩa là khi bộ sưu tập được sửa đổi và một luồng sử dụng một trình vòng lặp để duyệt qua bộ sưu tập, phương thức next() của trình vòng lặp sẽ đưa ra một ngoại lệ ConcurrentModificationException.
Các thùng chứa đồng thời hỗ trợ truyền tải đồng thời và cập nhật đồng thời.
Các lớp chính là ConcurrentHashMap, CopyOnWriteArrayList và CopyOnWriteArraySet. Hãy đọc bài viết này để tìm hiểu cách tránh ConcurrentModificationException.
8. Lớp Executor là gì?
Executors cung cấp một số phương thức tiện ích cho các lớp Executor, ExecutorService, ScheduledExecutorService, ThreadFactory và Callable.
Người thực thi có thể được sử dụng để dễ dàng tạo nhóm luồng.
Văn bản gốc: tạp chídev.com Dịch: ifeve Người dịch: Zheng Xudong.
Cuối cùng, bài viết này về các câu hỏi và câu trả lời phỏng vấn cơ bản về đa luồng và đồng thời JAVA (bản dịch) kết thúc tại đây. Nếu bạn muốn biết thêm về các câu hỏi và câu trả lời phỏng vấn cơ bản về đa luồng và đồng thời JAVA (bản dịch), vui lòng tìm kiếm bài viết CFSDN hoặc. tiếp tục Duyệt các bài viết liên quan, tôi hy vọng bạn sẽ ủng hộ blog của tôi trong tương lai! .
Tôi là một lập trình viên xuất sắc, rất giỏi!