CyStack logo
  • Sản phẩm & Dịch vụ
  • Giải pháp
  • Bảng giá
  • Công ty
  • Tài liệu
Vi

vi

Trang chủHướng dẫnCác trường hợp Deadlock trong Java và cách phòng tránh
Java

Các trường hợp Deadlock trong Java và cách phòng tránh

CyStack blog 6 phút để đọc
CyStack blog06/08/2025
Locker Avatar

Bao Tran

Web Developer

Locker logo social
Reading Time: 6 minutes

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ó.

Deadlock trong Java

Deadlock trong Java

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.

Cách phát hiện Deadlock trong Java

Để 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.

Cách tránh deadlock trong Java

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.

  • Tránh các Lock lồng nhau (Nested Lock): Đây là nguyên nhân phổ biến nhất gây ra deadlock. Hãy tránh khóa một tài nguyên nếu bạn đang giữ lock của một tài nguyên khác. Deadlock gần như không thể xảy ra nếu bạn chỉ làm việc với lock của một đối tượng duy nhất. Ví dụ, dưới đây là một cách triển khai thay thế cho phương thức 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.");
}
  • Chỉ khóa những gì cần thiết: Ta chỉ nên giành quyền lock trên những tài nguyên mà ta thật sự cần xử lý. Ví dụ, trong chương trình trên, ta đã khóa toàn bộ đối tượng Object. Tuy nhiên, nếu ta chỉ quan tâm đến một trong các trường (field) của nó, thì ta chỉ nên khóa trường cụ thể đó thay vì khóa toàn bộ đối tượng.
  • Tránh chờ đợi vô thời hạn: Deadlock có thể xảy ra nếu hai thread sử dụng phương thức 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.

Tổng kết

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.

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp 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.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất