- Sản phẩm & Dịch vụSản phẩm & Dịch vụ
- Giải phápGiải pháp
- Bảng giáBảng giá
- Công tyCông ty
- Tài liệuTài liệu
vi
vi
Chuyên mục với các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.
Web Developer
I’m passionate about web development and sharing my insights through articles, with over 8 years of experience. I hope these sharings inspire you and help build a strong web development community. @#@ Tôi đam mê phát triển web và chia sẻ những hiểu biết của mình thông qua các bài viết, với hơn 8 năm kinh nghiệm. Tôi hy vọng những chia sẻ này sẽ truyền cảm hứng cho các bạn và giúp xây dựng một cộng đồng phát triển web mạnh mẽ.
Thảo luận (0)
Đăng nhập để thảo luận
Deadlock trong Java là một tình huống trong lập trình khi hai hay nhiều thread bị khóa chặn vĩnh viễn. Trường hợp này xảy ra khi có ít nhất hai thread và hai (hoặc nhiều hơn) tài nguyên. Trong bài viết này, ta sẽ xem xét một ví dụ đơn giản gây ra deadlock, sau đó tìm hiểu cách phân tích nó.

Hãy cùng xem một chương trình đơn giản dùng để tạo ra deadlock giữa các thread trong Java.
package com.journaldev.threads;public class ThreadDeadlock {
public static void main(String[] args) throws InterruptedException { Object obj1 = new Object(); Object obj2 = new Object(); Object obj3 = new Object(); Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1"); Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2"); Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3"); t1.start(); Thread.sleep(5000); t2.start(); Thread.sleep(5000); t3.start(); }}
class SyncThread implements Runnable{ private Object obj1; private Object obj2;
public SyncThread(Object o1, Object o2){ this.obj1=o1; this.obj2=o2; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " acquiring lock on "+obj1); synchronized (obj1) { System.out.println(name + " acquired lock on "+obj1); work(); System.out.println(name + " acquiring lock on "+obj2); synchronized (obj2) { System.out.println(name + " acquired lock on "+obj2); work(); } System.out.println(name + " released lock on "+obj2); } System.out.println(name + " released lock on "+obj1); System.out.println(name + " finished execution."); } private void work() { try { Thread.sleep(30000); } catch (InterruptedException e) { e.printStackTrace(); } }}
Trong chương trình trên, SyncThread triển khai interface Runnable. Nó xử lý hai đối tượng bằng cách lần lượt chiếm quyền khóa (lock) trên từng đối tượng thông qua các khối synchronized (đồng bộ).
Trong phương thức main, ta khởi tạo ba thread chạy SyncThread và các thread này chia sẻ tài nguyên với nhau. Các thread được chạy theo một trình tự khiến chúng có thể giành được lock của đối tượng thứ nhất, nhưng khi cố gắng giành lock của đối tượng thứ hai, chúng sẽ rơi vào trạng thái chờ vì đối tượng này đã bị một thread khác khóa.
Điều này tạo ra một sự phụ thuộc vòng lặp về tài nguyên giữa các thread, dẫn đến deadlock. Khi thực thi chương trình, ta sẽ nhận được kết quả như dưới đây, nhưng chương trình sẽ không bao giờ kết thúc do deadlock.
t1 acquiring lock on java.lang.Object@6d9dd520 t1 acquired lock on java.lang.Object@6d9dd520 t2 acquiring lock on java.lang.Object@22aed3a5 t2 acquired lock on java.lang.Object@22aed3a5 t3 acquiring lock on java.lang.Object@218c2661 t3 acquired lock on java.lang.Object@218c2661 t1 acquiring lock on java.lang.Object@22aed3a5 t2 acquiring lock on java.lang.Object@218c2661 t3 acquiring lock on java.lang.Object@6d9dd520
Với kết quả trên, ta có thể dễ dàng nhận ra tình huống deadlock. Tuy nhiên, trong các ứng dụng thực tế, việc tìm ra và debug deadlock lại rất khó khăn.
Để phát hiện deadlock trong Java, ta cần xem xét thread dump (bản chụp trạng thái tất cả các luồng) của ứng dụng. Ta có thể tạo thread dump bằng công cụ như VisualVM hoặc jstack.
Dưới đây là thread dump của chương trình trên.
2012-12-27 19:08:34 Full thread dump Java HotSpot(TM) 64-Bit Server VM (23.5-b02 mixed mode):"Attach Listener" daemon prio=5 tid=0x00007fb0a2814000 nid=0x4007 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE
"DestroyJavaVM" prio=5 tid=0x00007fb0a2801000 nid=0x1703 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE
"t3" prio=5 tid=0x00007fb0a204b000 nid=0x4d07 waiting for monitor entry [0x000000015d971000] java.lang.Thread.State: BLOCKED (on object monitor) at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) - waiting to lock <0x000000013df2f658> (a java.lang.Object) - locked <0x000000013df2f678> (a java.lang.Object) at java.lang.Thread.run(Thread.java:722)
"t2" prio=5 tid=0x00007fb0a1073000 nid=0x4207 waiting for monitor entry [0x000000015d209000] java.lang.Thread.State: BLOCKED (on object monitor) at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) - waiting to lock <0x000000013df2f678> (a java.lang.Object) - locked <0x000000013df2f668> (a java.lang.Object) at java.lang.Thread.run(Thread.java:722)
"t1" prio=5 tid=0x00007fb0a1072000 nid=0x5503 waiting for monitor entry [0x000000015d86e000] java.lang.Thread.State: BLOCKED (on object monitor) at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) - waiting to lock <0x000000013df2f668> (a java.lang.Object) - locked <0x000000013df2f658> (a java.lang.Object) at java.lang.Thread.run(Thread.java:722)
"Service Thread" daemon prio=5 tid=0x00007fb0a1038000 nid=0x5303 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" daemon prio=5 tid=0x00007fb0a1037000 nid=0x5203 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" daemon prio=5 tid=0x00007fb0a1016000 nid=0x5103 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" daemon prio=5 tid=0x00007fb0a4003000 nid=0x5003 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE
"Finalizer" daemon prio=5 tid=0x00007fb0a4800000 nid=0x3f03 in Object.wait() [0x000000015d0c0000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x000000013de75798> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135) - locked <0x000000013de75798> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:177)
"Reference Handler" daemon prio=5 tid=0x00007fb0a4002000 nid=0x3e03 in Object.wait() [0x000000015cfbd000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x000000013de75320> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:503) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133) - locked <0x000000013de75320> (a java.lang.ref.Reference$Lock)
"VM Thread" prio=5 tid=0x00007fb0a2049800 nid=0x3d03 runnable
"GC task thread#0 (ParallelGC)" prio=5 tid=0x00007fb0a300d800 nid=0x3503 runnable
"GC task thread#1 (ParallelGC)" prio=5 tid=0x00007fb0a2001800 nid=0x3603 runnable
"GC task thread#2 (ParallelGC)" prio=5 tid=0x00007fb0a2003800 nid=0x3703 runnable
"GC task thread#3 (ParallelGC)" prio=5 tid=0x00007fb0a2004000 nid=0x3803 runnable
"GC task thread#4 (ParallelGC)" prio=5 tid=0x00007fb0a2005000 nid=0x3903 runnable
"GC task thread#5 (ParallelGC)" prio=5 tid=0x00007fb0a2005800 nid=0x3a03 runnable
"GC task thread#6 (ParallelGC)" prio=5 tid=0x00007fb0a2006000 nid=0x3b03 runnable
"GC task thread#7 (ParallelGC)" prio=5 tid=0x00007fb0a2006800 nid=0x3c03 runnable
"VM Periodic Task Thread" prio=5 tid=0x00007fb0a1015000 nid=0x5403 waiting on condition
JNI global references: 114
Found one Java-level deadlock:
"t3": waiting to lock monitor 0x00007fb0a1074b08 (object 0x000000013df2f658, a java.lang.Object), which is held by "t1" "t1": waiting to lock monitor 0x00007fb0a1010f08 (object 0x000000013df2f668, a java.lang.Object), which is held by "t2" "t2": waiting to lock monitor 0x00007fb0a1012360 (object 0x000000013df2f678, a java.lang.Object), which is held by "t3"
Java stack information for the threads listed above:
"t3": at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) - waiting to lock <0x000000013df2f658> (a java.lang.Object) - locked <0x000000013df2f678> (a java.lang.Object) at java.lang.Thread.run(Thread.java:722) "t1": at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) - waiting to lock <0x000000013df2f668> (a java.lang.Object) - locked <0x000000013df2f658> (a java.lang.Object) at java.lang.Thread.run(Thread.java:722) "t2": at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) - waiting to lock <0x000000013df2f678> (a java.lang.Object) - locked <0x000000013df2f668> (a java.lang.Object) at java.lang.Thread.run(Thread.java:722)
Found 1 deadlock.
Nội dung trên chỉ ra rõ tình huống deadlock, bao gồm các thread và tài nguyên có liên quan. Để phân tích deadlock, ta cần tìm các thread có trạng thái BLOCKED và các tài nguyên mà chúng đang chờ để khóa (waiting to lock).
Mỗi tài nguyên có một ID định danh duy nhất, qua đó ta có thể xác định được thread nào đang giữ lock của đối tượng đó. Ví dụ, thread “t3” đang chờ để khóa tài nguyên 0x000000013df2f658, nhưng tài nguyên này lại đang bị khóa bởi thread “t1”.
Sau khi phân tích và xác định được các thread gây ra deadlock, chúng ta cần phải thay đổi code để ngăn chặn tình huống này.
Dưới đây là một số nguyên tắc có thể giúp chúng ta tránh được hầu hết các tình huống deadlock.
run() không sử dụng lock lồng nhau, và chương trình sẽ chạy thành công mà không bị deadlock.public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + " acquiring lock on " + obj1);
synchronized (obj1) {
System.out.println(name + " acquired lock on " + obj1);
work();
}
System.out.println(name + " released lock on " + obj1);
System.out.println(name + " acquiring lock on " + obj2);
synchronized (obj2) {
System.out.println(name + " acquired lock on " + obj2);
work();
}
System.out.println(name + " released lock on " + obj2);
System.out.println(name + " finished execution.");
}
join() để chờ nhau hoàn thành một cách vô thời hạn. Nếu thread của bạn phải chờ một thread khác kết thúc, tốt nhất là hãy sử dụng join() với một khoảng thời gian chờ tối đa (timeout) mà bạn muốn.Nói chung, deadlock có thể xuất hiện do sự phụ thuộc giữa các thread trong quá trình truy cập tài nguyên. Bất kỳ hệ thống đa luồng nào cũng có thể gặp vấn đề này nếu không đặt ra quy tắc xử lý khóa rõ ràng. Bằng cách chủ động áp dụng một số quy tắc đơn giản ở trên, bạn có thể giảm thiểu khả năng xảy ra deadlock trong các ứng dụng của mình và đảm bảo nó vận hành ổn định hơn.