Deadlock trong Java

Chào mừng các bạn đã trở lại với thachleblog, hôm nay mình sẽ giới thiệu một chủ đề khá thú vị trong lập tình multi thread, đó là deadlock. Vậy deadlock là gì, làm thế nào để phát hiện deadlock? chúng ta cùng bắt đầu nhé.

Giới thiệu

Ở bài trước, khi sử dụng synchronization, thread sẽ chiếm giữ lock object để đảm bảo tại 1 thời điểm, chỉ có 1 thread được thực thi đoạn block code…

Vấn đề nảy sinh khi 2 hoặc nhiều thread giữ block object mà thread kia đang cần, dẫn đến tình trạng lock lẫn nhau

deadlock trong java

Hình minh họa tình huống dẫn đến deadlock (nguồn internet)

Vấn đề này chính là deadlock

Ví dụ

Chúng ta cùng xem code ví dụ xem điều gì xảy ra khi dealock nhé. Doạn code minh họa việc sử dụng synchronization và gây ra deadlock (tham khảo internet)

  1. package thach.le.deadlock;
  2. public class Task {
  3. private Object key1 = new Object();
  4. private Object key2 = new Object();
  5. public void task1() {
  6. synchronized (key1) {
  7. System.out.println("["+ Thread.currentThread().getName() + "]" + "I am doing task1");
  8. task2();
  9. }
  10. }
  11. public void task2() {
  12. synchronized (key2) {
  13. System.out.println("["+ Thread.currentThread().getName() + "]" + "I am doing task2");
  14. task3();
  15. }
  16. }
  17. public void task3() {
  18. synchronized (key1) {
  19. System.out.println("["+ Thread.currentThread().getName() + "]" + "I am doing task3");
  20. }
  21. }
  22.  
  23. public static void main(String[] args) throws InterruptedException {
  24. Task task = new Task();
  25. Runnable runable1 = () -> task.task1();
  26. Runnable runable2 = () -> task.task2();
  27. Thread thread1 = new Thread(runable1);
  28. thread1.start();
  29. Thread thread2 = new Thread(runable2);
  30. thread2.start();
  31. thread1.join();
  32. thread2.join();
  33. }
  34. }

Ở đoạn code trên mình có 2 thread:

  • Thread thứ nhất chạy method task1, method task1 có lock object là key1 đồng thời gọi method task2
  • Thread thứ hai chạy method task2, task 2 có lock object là key2 đồng thời gọi task3, task3 có lock object là key1

=> Thread thứ nhất có lock object là key1 và cần lock object key2 từ thread thứ hai, đồng thời thread thứ 2 có lock object là key2 và cần lock object là key1 từ thread 1 (hình minh họa ở trên)

Kết quả khi xảy ra deadlock

result

Chương trình bị “treo” (hai thread chờ nhau đến vô tận)
Chúng ta cùng debug để xem rõ hơn nhé:

debugdeadlock

Từ màn hình debug ta có thể thấy tình trạng của 2 thread-0 và thread-1:

  • Thread-0 đang nắm giữ Object id = 28 (key1) và đang đợi Object id = 30 (key1)
  • Thread-1 đang nắm giữ Object id = 30 (key2) và đang đợi Object id = 28 (key2

Làm thế nào để phát hiện deadlock?

Nếu không debug, chúng ta vẫn có thể phát hiện được deadlock bằng sử dụng jstack (trong thư mục bin của Java) bằng cách vào command line, gõ jstack – pid (pid của chương trình Java, trong Windows có thể xem trong Task Manager) xem thêm

Kết quả khi chạy jstack:

  1. Found one Java-level deadlock:
  2. =============================
  3. "Thread-1":
  4. waiting to lock monitor 0x00000000181f8a18 (object 0x00000000e096d858, a java.lang.Object),
  5. which is held by "Thread-0"
  6. "Thread-0":
  7. waiting to lock monitor 0x0000000015ddf518 (object 0x00000000e096d868, a java.lang.Object),
  8. which is held by "Thread-1"
  9.  
  10. Java stack information for the threads listed above:
  11. ===================================================
  12. "Thread-1":
  13. at thach.le.deadlock.Task.task3(Task.java:24)
  14. - waiting to lock (a java.lang.Object)
  15. at thach.le.deadlock.Task.task2(Task.java:18)
  16. - locked (a java.lang.Object)
  17. at thach.le.deadlock.Task.lambda$1(Task.java:32)
  18. at thach.le.deadlock.Task$$Lambda$2/1359044626.run(Unknown Source)
  19. at java.lang.Thread.run(Unknown Source)
  20. "Thread-0":
  21. at thach.le.deadlock.Task.task2(Task.java:17)
  22. - waiting to lock (a java.lang.Object)
  23. at thach.le.deadlock.Task.task1(Task.java:11)
  24. - locked (a java.lang.Object)
  25. at thach.le.deadlock.Task.lambda$0(Task.java:31)
  26. at thach.le.deadlock.Task$$Lambda$1/665576141.run(Unknown Source)
  27. at java.lang.Thread.run(Unknown Source)
  28.  
  29. Found 1 deadlock.

Từ kết quả in ra sau khi chạy jstack. Chúng ta có thể đọc được và xác định được đoạn code gây ra deadlock từ đó có biện pháp thích hợp để ngăn chặn. Cách thông thường là tránh trường hợp các object phải đợi lẫn nhau (có thể cho thread đợi thread khác thực thi xong).

Deadlock là một chủ đề khó trong Java, do đó, qua bài chia sẻ này, mình mong rằng có thể giúp các bạn hiểu hơn về deadlock. Rất mong nhận được ý kiến đóng góp của tất cả các bạn.

Xin cảm ơn.

Tham khảo thêm
https://app.pluralsight.com/player?course=java-patterns-concurrency-multi-threading
https://docs.oracle.com/javase/tutorial/essential/concurrency/deadlock.html