Deadlock and Livelock

Deadlock and Livelock

Deadlocklivelock là hai vấn đề phổ biến trong lập trình đa luồng hoặc hệ thống song song, xảy ra khi các thread không thể tiếp tục tiến hành công việc của mình. Bài viêt này chúng ta sẽ cùng tim hiểu về 2 vấn để khó nhằn này trong multi-threads programing.


1. Deadlock

Định nghĩa:
Deadlock xảy ra khi hai hoặc nhiều thread bị chặn mãi mãi vì mỗi thread đang chờ tài nguyên mà một thread khác đang giữ, tạo thành một vòng tròn phụ thuộc.

Điều kiện xảy ra Deadlock:

Để xảy ra deadlock, cần có 4 điều kiện (theo Coffman):

  1. Mutual Exclusion (Loại trừ lẫn nhau): Tài nguyên chỉ có thể được một thread sử dụng tại một thời điểm.

  2. Hold and Wait (Giữ và chờ): Thread giữ một tài nguyên và đang chờ để nhận thêm tài nguyên khác.

  3. No Preemption (Không bị cưỡng chế): Tài nguyên không thể bị cưỡng chế lấy lại từ thread đang giữ nó.

  4. Circular Wait (Chờ vòng tròn): Một chuỗi các thread chờ nhau để giải phóng tài nguyên (A chờ B, B chờ C, và C chờ A).

Ví dụ minh họa:

class DeadlockExample {
    private static final Object resource1 = new Object();
    private static final Object resource2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println("Thread 1: Locked resource 1");
                try { Thread.sleep(50); } catch (InterruptedException ignored) {}
                synchronized (resource2) {
                    System.out.println("Thread 1: Locked resource 2");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (resource2) {
                System.out.println("Thread 2: Locked resource 2");
                try { Thread.sleep(50); } catch (InterruptedException ignored) {}
                synchronized (resource1) {
                    System.out.println("Thread 2: Locked resource 1");
                }
            }
        });

        t1.start();
        t2.start();
    }
}

Kết quả:

  • Thread 1 khóa resource1 và chờ resource2.

  • Thread 2 khóa resource2 và chờ resource1.

  • Không thread nào có thể tiến lên được -> Deadlock.


2. Livelock

Định nghĩa:
Livelock xảy ra khi hai hoặc nhiều thread vẫn hoạt động (không bị chặn) nhưng liên tục thay đổi trạng thái của mình để phản ứng với trạng thái của thread khác, dẫn đến không thread nào tiến lên được.

Đặc điểm:

  • Khác với deadlock, các thread trong livelock không bị chặn và vẫn tiếp tục thực hiện công việc của mình.

  • Tuy nhiên, trạng thái của hệ thống vẫn không thay đổi, và không thread nào hoàn thành được nhiệm vụ.

Ví dụ minh họa:

Giả sử có hai người cùng bước sang trái hoặc phải để tránh nhau trên đường đi, nhưng họ liên tục thay đổi hướng cùng một lúc và không thể vượt qua nhau.

class LivelockExample {
    static class Resource {
        private boolean inUse = false;

        public synchronized boolean isInUse() {
            return inUse;
        }

        public synchronized void setInUse(boolean inUse) {
            this.inUse = inUse;
        }
    }

    public static void main(String[] args) {
        Resource resource1 = new Resource();
        Resource resource2 = new Resource();

        Thread t1 = new Thread(() -> {
            while (true) {
                if (!resource1.isInUse()) {
                    resource1.setInUse(true);
                    if (!resource2.isInUse()) {
                        System.out.println("Thread 1: Acquired both resources!");
                        resource2.setInUse(true);
                        break;
                    } else {
                        resource1.setInUse(false);
                        System.out.println("Thread 1: Released resource 1 to retry...");
                    }
                }
            }
        });

        Thread t2 = new Thread(() -> {
            while (true) {
                if (!resource2.isInUse()) {
                    resource2.setInUse(true);
                    if (!resource1.isInUse()) {
                        System.out.println("Thread 2: Acquired both resources!");
                        resource1.setInUse(true);
                        break;
                    } else {
                        resource2.setInUse(false);
                        System.out.println("Thread 2: Released resource 2 to retry...");
                    }
                }
            }
        });

        t1.start();
        t2.start();
    }
}

Kết quả:

  • Thread 1 chiếm được resource1 nhưng liên tục nhả ra vì không thể chiếm resource2.

  • Thread 2 chiếm được resource2 nhưng cũng liên tục nhả ra vì không thể chiếm resource1.

  • Cả hai thread không bị chặn nhưng không tiến lên được -> Livelock.


3. So sánh Deadlock và Livelock

Tiêu chíDeadlockLivelock
Trạng thái threadCác thread bị chặn (không hoạt động).Các thread vẫn hoạt động nhưng không tiến triển.
Nguyên nhânXảy ra do các thread chờ tài nguyên lẫn nhau.Xảy ra do các thread liên tục thay đổi trạng thái để phản ứng với nhau.
Cách phát hiệnDễ phát hiện vì các thread không hoạt động.Khó phát hiện hơn vì các thread vẫn hoạt động.
Khả năng khắc phụcThay đổi thứ tự khóa tài nguyên, sử dụng timeout.Thêm logic ngắt vòng lặp hoặc giới hạn số lần thử.

4. Cách phòng tránh Deadlock và Livelock

Phòng tránh Deadlock:

  1. Tránh chờ vòng tròn (Circular Wait):

    • Đảm bảo tất cả các thread yêu cầu tài nguyên theo thứ tự cố định.
  2. Áp dụng timeout:

    • Sử dụng timeout khi chờ tài nguyên để tránh chờ vô hạn.
  3. Giảm khóa không cần thiết:

    • Sử dụng khóa ở mức độ nhỏ nhất có thể.
  4. Sử dụng các công cụ hỗ trợ:

    • Sử dụng các thư viện như java.util.concurrent (ReentrantLock, Semaphore, v.v.) thay vì synchronized.

Phòng tránh Livelock:

  1. Ngẫu nhiên hóa hành vi:

    • Các thread đưa ra quyết định dựa trên logic ngẫu nhiên để tránh xung đột liên tục.
  2. Giới hạn số lần thử:

    • Thêm số lần thử tối đa hoặc cơ chế phá vòng lặp.
  3. Thiết kế lại giao thức:

    • Xem xét logic tương tác giữa các thread để tránh phản ứng liên tục.