JavaRush /Blog Java /Random-VI /Bản dịch: 50 câu hỏi phỏng vấn hàng đầu theo chủ đề. Phần...
KapChook
Mức độ
Volga

Bản dịch: 50 câu hỏi phỏng vấn hàng đầu theo chủ đề. Phần 2.

Xuất bản trong nhóm
Phần thứ hai trong bản dịch của bài viết gốc Top 50 câu trả lời phỏng vấn về Java Thread dành cho người mới bắt đầu, lập trình viên có kinh nghiệm. Phần đầu tiên.
  1. Làm cách nào để kiểm tra xem một sợi có đang giữ khóa không?

  2. Tôi không biết rằng bạn có thể kiểm tra xem một chủ đề hiện có đang giữ khóa hay không cho đến khi tôi gặp câu hỏi này trong một cuộc phỏng vấn qua điện thoại. java.lang.Thread có phương thức holdLock(), nó trả về true khi và chỉ khi luồng hiện tại đang giữ màn hình trên một đối tượng cụ thể.
  3. Làm thế nào để có được một kết xuất chủ đề?

  4. Kết xuất luồng cho phép bạn tìm hiểu xem một luồng hiện đang làm gì. Có một số cách để có được kết xuất luồng, tùy thuộc vào hệ điều hành. Trên Windows bạn có thể sử dụng tổ hợp ctrl + Break, trên Linux bạn có thể sử dụng lệnh kill -3. Bạn cũng có thể sử dụng tiện ích jstack; nó hoạt động trên id tiến trình mà bạn có thể tìm hiểu bằng cách sử dụng tiện ích jps khác.
  5. Tham số JVM nào được sử dụng để kiểm soát kích thước ngăn xếp của luồng?

  6. Đây là một trong những tham số -Xss đơn giản được sử dụng để kiểm soát kích thước ngăn xếp của một luồng trong Java.
  7. Sự khác biệt giữa đồng bộ hóa và ReentrantLock?

  8. Đôi khi cách duy nhất để đạt được sự loại trừ lẫn nhau là thông qua từ khóa được đồng bộ hóa, nhưng nó có một số nhược điểm, chẳng hạn như không thể mở rộng khóa ra ngoài một phương thức hoặc khối mã, v.v. Java 5 giải quyết vấn đề này bằng cách cung cấp khả năng kiểm soát chi tiết hơn thông qua giao diện Lock. ReentrantLock là một triển khai Khóa phổ biến cung cấp Khóa có hành vi và ngữ nghĩa cơ bản giống như một trình giám sát ngầm, đạt được bằng cách sử dụng các phương pháp được đồng bộ hóa nhưng có khả năng nâng cao.
  9. Cho 3 luồng T1, T2 và T3? Thực hiện trình tự T1, T2, T3 như thế nào?

  10. Tính nhất quán có thể đạt được bằng nhiều cách, nhưng bạn chỉ cần sử dụng phương thức join() để bắt đầu một luồng khi một luồng khác đã thực thi xong. Để thực hiện trình tự đã cho, trước tiên bạn cần bắt đầu luồng cuối cùng, sau đó gọi phương thức join() theo thứ tự ngược lại, tức là T3 gọi T2.join và T2 gọi T1.join, do đó T1 sẽ hoàn thành trước và T3 cuối cùng .
  11. Phương pháp năng suất làm gì?

  12. Phương thức lợi nhuận là một cách để yêu cầu một luồng từ bỏ CPU để một luồng khác có thể thực thi. Đây là một phương thức tĩnh và nó chỉ đảm bảo rằng luồng hiện tại sẽ từ bỏ bộ xử lý, nhưng không quyết định việc thực thi luồng nào sẽ đi đến.
  13. Mức độ tương tranh của ConcurrentHashMap là gì?

  14. ConcurrentHashMap đạt được khả năng mở rộng và an toàn luồng bằng cách chia bản đồ thực tế thành các phần. Sự tách biệt này đạt được bằng cách sử dụng mức độ song song. Đây là tham số tùy chọn cho hàm tạo ConcurrentHashMap và giá trị mặc định của nó là 16.
  15. Semaphore là gì?

  16. Semaphore là một loại đồng bộ hóa mới. Đây là một semaphore với một bộ đếm. Về mặt khái niệm, một semaphore kiểm soát một tập hợp các quyền. Mỗi khối thu được (), nếu cần, trước khi có quyền, sau đó sẽ lấy được nó. Mỗi bản phát hành() thêm một quyền, có khả năng giải phóng trình thu thập chặn. Tuy nhiên, điều này không sử dụng các đối tượng cấp phép thực tế; Semaphore chỉ lưu trữ số lượng có sẵn và hành động tương ứng. Semaphore được sử dụng để bảo vệ các tài nguyên đắt tiền có sẵn với số lượng hạn chế, chẳng hạn như kết nối tới cơ sở dữ liệu gộp.
  17. Điều gì xảy ra nếu hàng đợi nhóm luồng đã đầy và bạn gửi tác vụ?

  18. Nếu hàng đợi nhóm luồng đã đầy, tác vụ đã gửi sẽ bị "từ chối". Phương thức submit() của ThreadPoolExecutor ném ra một ngoại lệ RejectedExecutionException, sau đó RejectedExecutionHandler được gọi.
  19. Sự khác nhau giữa các phương thức submit() và exec() trên nhóm luồng?

  20. Cả hai phương pháp đều là cách gửi tác vụ đến nhóm luồng, nhưng có một chút khác biệt giữa chúng. Thực thi (Lệnh có thể chạy) được xác định trong giao diện Executor và thực thi tác vụ nhất định trong tương lai, nhưng quan trọng hơn là không trả về bất cứ điều gì. Mặt khác, submit() là một phương thức quá tải, nó có thể chấp nhận các tác vụ Runnable và Callable, đồng thời có thể trả về một đối tượng Future có thể được sử dụng để hủy thực thi và/hoặc chờ kết quả tính toán. Phương thức này được xác định trong giao diện ExecutorService kế thừa từ giao diện Executor và mỗi lớp nhóm luồng, chẳng hạn như ThreadPoolExecutor hoặc ScheduledThreadPoolExecutor, kế thừa các phương thức này.
  21. Phương pháp chặn là gì?

  22. Phương thức chặn là phương thức chặn cho đến khi tác vụ được hoàn thành, ví dụ: phương thức ServerSocket Accept() chặn trong khi chờ máy khách kết nối. Ở đây, việc chặn có nghĩa là quyền điều khiển sẽ không quay trở lại phương thức gọi cho đến khi công việc hoàn thành. Mặt khác, có những phương thức không đồng bộ hoặc không chặn sẽ hoàn thành trước khi tác vụ hoàn thành.
  23. Chủ đề Swing có an toàn không?

  24. Nói một cách đơn giản là không, Swing không an toàn theo luồng, nhưng bạn cần giải thích ý của bạn khi nói điều đó, ngay cả khi người phỏng vấn không hỏi. Khi chúng tôi nói rằng Swing không an toàn cho luồng, chúng tôi thường đề cập đến thực tế rằng đó là một thành phần không thể được sửa đổi bởi nhiều luồng. Tất cả các thay đổi đối với các thành phần GUI phải được thực hiện trong luồng AWT và Swing cung cấp các phương thức đồng bộ và không đồng bộ để lập lịch các thay đổi đó.
  25. Sự khác biệt giữa InvovAndWait và InvokeLater?

  26. Đây là hai phương thức API Swing cho phép nhà phát triển cập nhật các thành phần GUI từ các luồng thay vì từ luồng Trình quản lý sự kiện. InvokeAndWait() cập nhật đồng bộ một thành phần GUI chẳng hạn như thanh tiến trình; mỗi lần tiến trình được thực hiện, thanh này phải được cập nhật để phản ánh các thay đổi. Nếu tiến trình đang được theo dõi trong một luồng khác, nó phải gọi hàm InvoveAndWait() để chỉ định luồng Trình điều phối sự kiện cập nhật thành phần đó. VàgọiLater() là lệnh gọi không đồng bộ để cập nhật các thành phần.
  27. Phương pháp API Swing nào an toàn cho luồng?

  28. Câu hỏi này một lần nữa là về Swing và độ an toàn của luồng, mặc dù các thành phần Swing không an toàn cho luồng, nhưng có những phương thức có thể được gọi một cách an toàn từ nhiều luồng. Tôi biết rằng repaint() và revalidate() là an toàn theo luồng, nhưng có các phương pháp khác trên các thành phần Swing khác nhau, chẳng hạn như các phương thức setText() của JTextComponent và các phương thức chèn() vàappend() của JTextArea.
  29. Làm thế nào để tạo ra các đối tượng bất biến?

  30. Câu hỏi này có vẻ không liên quan gì đến đa luồng và đồng thời, nhưng thực tế là có. Tính bất biến giúp đơn giản hóa mã song song vốn đã phức tạp. Một đối tượng bất biến sẽ rất tốn kém đối với các nhà phát triển vì nó có thể được phổ biến mà không cần bất kỳ sự đồng bộ hóa nào. Thật không may, Java không có chú thích @Immutable, điều này sẽ làm cho đối tượng của bạn không thể thay đổi được, vì điều này các nhà phát triển cần phải làm việc chăm chỉ. Để tạo một đối tượng bất biến, bạn cần tuân theo những điều cơ bản: khởi tạo trong hàm tạo, không setters, không rò rỉ tham chiếu, lưu trữ các bản sao riêng biệt của các đối tượng có thể thay đổi.
  31. ReadWriteLock là gì?

  32. Nói chung, ReadWriteLock là kết quả của kỹ thuật phân tích cú pháp khóa nhằm cải thiện hiệu suất của các ứng dụng song song. Đây là giao diện đã được thêm vào trong Java 5. Nó hoạt động trên một cặp khóa liên quan, một khóa dành cho thao tác đọc, một khóa dành cho thao tác ghi. Khóa đầu đọc có thể được giữ đồng thời bởi nhiều luồng đọc cho đến khi không còn đầu ghi nào. Khóa viết là độc quyền. Nếu muốn, bạn có thể triển khai giao diện với bộ quy tắc của mình hoặc bạn có thể sử dụng ReentrantReadWriteLock, hỗ trợ tối đa 65535 khóa ghi đệ quy và 65535 khóa đọc.
  33. Quay bận rộn là gì?

  34. Busy Spin là một kỹ thuật mà các lập trình viên sử dụng để buộc một luồng phải chờ trong một điều kiện nhất định. Không giống như các phương thức truyền thống, Wait(), sleep() hoặc Yield(), liên quan đến việc nhường lại quyền kiểm soát bộ xử lý, phương thức này không nhượng lại bộ xử lý; thay vào đó, nó chỉ thực hiện một vòng lặp trống. Tại sao mọi người sẽ làm điều này? Để lưu bộ nhớ đệm của bộ xử lý. Trên các hệ thống đa lõi, có thể một luồng bị treo sẽ tiếp tục thực thi trên lõi khác, điều đó có nghĩa là phải xây dựng lại bộ đệm. Để tránh việc xây dựng lại tốn kém, người lập trình thích chờ ít hơn bằng cách sử dụng vòng quay bận.
  35. Sự khác biệt giữa các biến dễ bay hơi và nguyên tử?

  36. Đây là một câu hỏi khá thú vị, lúc đầu các biến dễ bay hơi và nguyên tử trông rất giống nhau, nhưng chúng vẫn khác nhau. Một biến dễ bay hơi cung cấp một sự đảm bảo xảy ra trước đó rằng việc ghi sẽ xảy ra trước bất kỳ lần ghi tiếp theo nào; nó không đảm bảo tính nguyên tử. Ví dụ: thao tác count++ sẽ không trở thành nguyên tử chỉ vì count được khai báo là không ổn định. Mặt khác, lớp AtomicInteger cung cấp một phương thức nguyên tử để thực hiện các phép toán phức tạp như vậy một cách nguyên tử, ví dụ getAndIncrement() là một sự thay thế nguyên tử cho toán tử tăng dần, nó có thể được sử dụng để tăng nguyên tử giá trị hiện tại lên một. Ngoài ra còn có các phiên bản nguyên tử cho các loại dữ liệu khác.
  37. Điều gì xảy ra nếu một luồng ném Ngoại lệ vào một khối được đồng bộ hóa?

  38. Đây là một câu hỏi mẹo khác dành cho những người lập trình Java thông thường. Bất kể bạn thoát khỏi khối được đồng bộ hóa bằng cách nào, thông thường bằng cách kết thúc quá trình thực thi hoặc đột ngột bằng cách ném một ngoại lệ, luồng sẽ giải phóng khóa thu được khi nó đi vào khối được đồng bộ hóa. Đây là một trong những lý do tại sao tôi thích khối khóa được đồng bộ hóa hơn là giao diện yêu cầu sự cẩn thận đặc biệt khi giải phóng khóa, thường đạt được bằng cách giải phóng khóa trong khối cuối cùng.
  39. Khóa kiểm tra kép của Singleton là gì?

  40. Đây là một trong những câu hỏi phỏng vấn phổ biến nhất, tuy nhiên, mặc dù nó rất phổ biến nhưng khả năng ứng viên trả lời nó cao nhất là 50%. Một nửa thời gian họ thất bại trong việc viết mã và nửa còn lại là giải thích cách nó bị hỏng và được sửa trong Java 1.5. Đây là một cách cũ để tạo một singleton an toàn theo luồng, cố gắng tối ưu hóa hiệu suất bằng cách chỉ chặn khi phiên bản singleton được khởi tạo lần đầu tiên, nhưng do tính phức tạp và thực tế là nó đã bị hỏng trong JDK 1.4 nên cá nhân tôi không thích Nó. Tuy nhiên, ngay cả khi bạn không thích cách tiếp cận này, thì việc biết nó từ góc độ phỏng vấn cũng rất hữu ích.
  41. Làm cách nào để tạo một Singleton an toàn theo chủ đề?

  42. Câu hỏi này bổ sung cho câu hỏi trước. Nếu bạn nói rằng bạn không thích khóa được kiểm tra hai lần thì người phỏng vấn sẽ buộc phải hỏi về các cách khác để tạo Singleton an toàn theo luồng. Và đúng như vậy, bạn có thể tận dụng các tính năng tải lớp và khởi tạo biến tĩnh để khởi tạo Singleton hoặc bạn có thể tận dụng loại enum mạnh mẽ.
  43. Hãy liệt kê 3 phong tục bạn tuân theo trong lập trình song song?

  44. Đây là câu hỏi yêu thích của tôi vì tôi tin rằng có một số quy tắc nhất định cần phải tuân theo khi viết mã song song, điều này giúp cải thiện hiệu suất, gỡ lỗi và hỗ trợ. Dưới đây là 3 quy tắc tốt nhất mà tôi tin rằng mọi lập trình viên Java nên tuân theo:
    • Luôn đặt tên có ý nghĩa cho chủ đề của bạn
    • Tìm lỗi hoặc theo dõi ngoại lệ trong mã song song là một nhiệm vụ khá khó khăn. OrderProcessor, QuoteProcessor hoặc TradeProcessor tốt hơn nhiều so với Thread-1. Chủ đề-2 và Chủ đề-3. Tên phải phản ánh tác vụ được thực hiện bởi luồng. Tất cả các framework chính và thậm chí cả JDK đều tuân theo quy tắc này.
    • Tránh chặn hoặc giảm phạm vi đồng bộ hóa
    • Việc chặn rất tốn kém và việc chuyển ngữ cảnh thậm chí còn tốn kém hơn. Cố gắng tránh đồng bộ hóa và chặn càng nhiều càng tốt, và bạn sẽ giảm phần quan trọng xuống mức tối thiểu cần thiết. Đây là lý do tại sao tôi thích chặn theo thời gian hơn phương pháp tính giờ vì nó cho phép bạn kiểm soát tuyệt đối phạm vi chặn.
    • Giữa đồng bộ hóa và chờ và thông báo, hãy chọn đồng bộ hóa
    • Thứ nhất, các trình đồng bộ hóa như CountDownLatch, Semaphore, CyclicBarrier hoặc Exchanger giúp đơn giản hóa việc mã hóa. Rất khó để thực hiện luồng điều khiển phức tạp bằng cách chờ và thông báo. Thứ hai, các lớp này được viết và duy trì bởi những người giỏi nhất trong doanh nghiệp và rất có thể chúng sẽ được tối ưu hóa hoặc thay thế bằng mã tốt hơn trong các bản phát hành JDK trong tương lai. Bằng cách sử dụng các tiện ích đồng bộ hóa cấp cao, bạn sẽ tự động nhận được tất cả những lợi ích này.
    • Giữa Bộ sưu tập đồng thời và Bộ sưu tập được đồng bộ hóa, hãy chọn Bộ sưu tập đồng thời
    • Đây là một quy tắc đơn giản khác, dễ thực hiện và thu được lợi ích. Các bộ sưu tập đồng thời có khả năng mở rộng cao hơn so với các bộ sưu tập được đồng bộ hóa của chúng, vì vậy tốt hơn nên sử dụng chúng khi viết mã song song. Vì vậy, lần tới khi bạn cần một bản đồ, hãy nghĩ đến ConcurrentHashMap trước khi nghĩ đến Hashtable.
  45. Làm thế nào để buộc một chủ đề bắt đầu?

  46. Đây là câu hỏi về cách buộc bộ sưu tập rác chạy. Tóm lại, không thể nào, tất nhiên bạn có thể thực hiện truy vấn bằng System.gc(), nhưng nó không đảm bảo bất cứ điều gì. Hoàn toàn không có cách nào để buộc một luồng bắt đầu trong Java, điều này được điều khiển bởi bộ lập lịch luồng và Java không cung cấp bất kỳ API nào để kiểm soát nó. Phần này của Java vẫn còn ngẫu nhiên.
  47. Khung Fork/Join là gì?

  48. Khung Fork/Join được giới thiệu trong JDK 7 là một tiện ích mạnh mẽ cho phép nhà phát triển tận dụng nhiều bộ xử lý của các máy chủ hiện đại. Nó được thiết kế cho công việc có thể được chia nhỏ thành các phần tử nhỏ một cách đệ quy. Mục tiêu là sử dụng tất cả sức mạnh tính toán sẵn có để tăng hiệu suất cho ứng dụng của bạn. Một ưu điểm đáng kể của framework này là nó sử dụng thuật toán lấy cắp công việc (từ công việc - công việc và ăn cắp - để ăn cắp). Các luồng công nhân đã hết công việc có thể cướp công việc từ các luồng khác vẫn đang bận.
  49. Sự khác biệt giữa các lệnh gọi wait() và sleep() là gì?

  50. Mặc dù cả chờ và ngủ đều đại diện cho một loại tạm dừng trong ứng dụng Java, nhưng chúng là thiết bị dành cho các nhu cầu khác nhau. Chờ được sử dụng để liên lạc với luồng nội bộ, nó sẽ khóa nếu điều kiện chờ là đúng và chờ thông báo nếu hành động của luồng khác làm cho điều kiện chờ là sai. Mặt khác, phương thức sleep() chỉ đơn giản là từ bỏ bộ xử lý hoặc dừng luồng hiện tại thực thi trong một khoảng thời gian xác định. Việc gọi hàm sleep() không giải phóng khóa được giữ bởi luồng hiện tại.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION