Deadlock và livelock 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):
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.
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.
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ó.
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ếmresource2
.Thread 2 chiếm được
resource2
nhưng cũng liên tục nhả ra vì không thể chiếmresource1
.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í | Deadlock | Livelock |
Trạng thái thread | Cá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ân | Xả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ện | Dễ 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ục | Thay đổ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:
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.
Áp dụng timeout:
- Sử dụng timeout khi chờ tài nguyên để tránh chờ vô hạn.
Giảm khóa không cần thiết:
- Sử dụng khóa ở mức độ nhỏ nhất có thể.
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
.
- Sử dụng các thư viện như
Phòng tránh Livelock:
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.
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.
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.