Java Deadlock
Deadlock Nədir?
Deadlock iki və ya daha çox thread-in bir-birinin tutduğu resursları gözləməsi və heç birinin davam edə bilməməsi vəziyyətidir. Thread-lər qarşılıqlı gözləmədə qalır və proqram "donur".
Deadlock Şərtləri
Deadlock baş verməsi üçün 4 şərt eyni vaxtda mövcud olmalıdır:
- Mutual Exclusion - Resurs yalnız bir thread tərəfindən istifadə edilə bilər
- Hold and Wait - Thread resurs tutaraq digərini gözləyir
- No Preemption - Resurs zorla alına bilməz
- Circular Wait - Thread-lər dairəvi gözləmə zənciri yaradır
Klassik Deadlock Nümunəsi
public class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
// Thread 1: lock1 -> lock2
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1: lock1 alındı");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: lock2 gözləyir...");
synchronized (lock2) {
System.out.println("Thread 1: lock2 alındı");
}
}
});
// Thread 2: lock2 -> lock1
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2: lock2 alındı");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: lock1 gözləyir...");
synchronized (lock1) {
System.out.println("Thread 2: lock1 alındı");
}
}
});
t1.start();
t2.start();
// Deadlock baş verir!
// Thread 1: lock1-i tutur, lock2-ni gözləyir
// Thread 2: lock2-ni tutur, lock1-i gözləyir
}
}
Bank Transfer Deadlock
class Account {
private int balance;
private final int id;
public Account(int id, int balance) {
this.id = id;
this.balance = balance;
}
public int getId() {
return id;
}
public void withdraw(int amount) {
balance -= amount;
}
public void deposit(int amount) {
balance += amount;
}
public int getBalance() {
return balance;
}
}
// YANLIŞ - Deadlock riski
class DeadlockTransfer {
public void transfer(Account from, Account to, int amount) {
synchronized (from) {
synchronized (to) {
from.withdraw(amount);
to.deposit(amount);
}
}
}
}
// Scenario:
// Thread 1: transfer(A, B, 100) - A-nı lock edir, B-ni gözləyir
// Thread 2: transfer(B, A, 50) - B-ni lock edir, A-nı gözləyir
// DEADLOCK!
Deadlock Prevention (Qarşısını Almaq)
1. Lock Ordering (Lock Sıralaması)
class SafeTransfer {
public void transfer(Account from, Account to, int amount) {
Account first, second;
// Həmişə eyni sıra ilə lock al
if (from.getId() < to.getId()) {
first = from;
second = to;
} else {
first = to;
second = from;
}
synchronized (first) {
synchronized (second) {
from.withdraw(amount);
to.deposit(amount);
}
}
}
}
// İndi hər iki thread eyni sıra ilə lock alır
// Thread 1: transfer(A, B) -> A, sonra B
// Thread 2: transfer(B, A) -> A, sonra B (B-dən əvvəl A)
// Deadlock olmur!
2. tryLock() with Timeout
class TryLockTransfer {
public boolean transfer(Account from, Account to, int amount) {
Lock lock1 = from.getLock();
Lock lock2 = to.getLock();
while (true) {
boolean gotLock1 = false;
boolean gotLock2 = false;
try {
gotLock1 = lock1.tryLock();
gotLock2 = lock2.tryLock();
if (gotLock1 && gotLock2) {
// Hər iki lock alındı
from.withdraw(amount);
to.deposit(amount);
return true;
}
} finally {
if (gotLock1) {
lock1.unlock();
}
if (gotLock2) {
lock2.unlock();
}
}
// Bir az gözlə və yenidən cəhd et
try {
Thread.sleep(10);
} catch (InterruptedException e) {
return false;
}
}
}
}
3. tryLock() with Timeout (Daha Sadə)
class TimeoutTransfer {
public boolean transfer(Account from, Account to, int amount)
throws InterruptedException {
Lock lock1 = from.getLock();
Lock lock2 = to.getLock();
long timeout = 1000; // 1 saniyə
long startTime = System.currentTimeMillis();
while (true) {
if (lock1.tryLock(timeout, TimeUnit.MILLISECONDS)) {
try {
if (lock2.tryLock(timeout, TimeUnit.MILLISECONDS)) {
try {
from.withdraw(amount);
to.deposit(amount);
return true;
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
// Timeout check
if (System.currentTimeMillis() - startTime > timeout) {
return false;
}
}
}
}
4. Single Lock Strategy
class SingleLockTransfer {
private static final Object TRANSFER_LOCK = new Object();
public void transfer(Account from, Account to, int amount) {
// Bütün transfer-lər üçün eyni lock
synchronized (TRANSFER_LOCK) {
from.withdraw(amount);
to.deposit(amount);
}
}
}
// Sadə, amma performans aşağı ola bilər
Deadlock Detection (Aşkarlama)
Thread Dump ilə
class DeadlockDetector {
public static void detectDeadlock() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
System.out.println("Deadlock aşkarlandı!");
ThreadInfo[] threadInfos =
threadMXBean.getThreadInfo(deadlockedThreads);
for (ThreadInfo info : threadInfos) {
System.out.println("Thread: " + info.getThreadName());
System.out.println("State: " + info.getThreadState());
System.out.println("Locked on: " + info.getLockName());
System.out.println();
}
} else {
System.out.println("Deadlock tapılmadı");
}
}
}
// Periodic check
class DeadlockMonitor extends Thread {
@Override
public void run() {
while (true) {
DeadlockDetector.detectDeadlock();
try {
Thread.sleep(5000); // 5 saniyədə bir check et
} catch (InterruptedException e) {
break;
}
}
}
}
JConsole və VisualVM
# Thread dump almaq
jstack <pid>
# JConsole-da
# Threads tab -> "Detect Deadlock" button
Real-World Nümunələr
Database Connection Pool Deadlock
class ConnectionPoolDeadlock {
private final List<Connection> pool;
private final int maxConnections = 2;
// PROBLEM: Nested connection istifadəsi
public void problematicMethod() throws SQLException {
Connection conn1 = getConnection();
try {
// İşlə
nestedCall(conn1);
} finally {
releaseConnection(conn1);
}
}
private void nestedCall(Connection conn1) throws SQLException {
// Yeni connection lazımdır, amma pool-da yer yoxdur
Connection conn2 = getConnection(); // Deadlock!
try {
// İşlə
} finally {
releaseConnection(conn2);
}
}
// HƏLL: Connection-ı parameter olaraq ötür
public void betterMethod() throws SQLException {
Connection conn = getConnection();
try {
doWork(conn);
} finally {
releaseConnection(conn);
}
}
private void doWork(Connection conn) throws SQLException {
// Eyni connection-ı istifadə et
}
}
Resource Hierarchy Solution
class ResourceHierarchy {
// Resource-ları hierarchy ilə təşkil et
private static final Object DATABASE_LOCK = new Object(); // Level 1
private static final Object CACHE_LOCK = new Object(); // Level 2
private static final Object FILE_LOCK = new Object(); // Level 3
// DÜZGÜN - Həmişə level sırası ilə lock al
public void correctMethod() {
synchronized (DATABASE_LOCK) { // Level 1
synchronized (CACHE_LOCK) { // Level 2
synchronized (FILE_LOCK) { // Level 3
// Work
}
}
}
}
// YANLIŞ - Hierarchy pozulur
public void wrongMethod() {
synchronized (CACHE_LOCK) { // Level 2
synchronized (DATABASE_LOCK) { // Level 1 - SƏHV SIRA!
// Deadlock riski
}
}
}
}
Best Practices
-
Lock ordering istifadə edin:
// Həmişə eyni sıra ilə lock alın
if (account1.getId() < account2.getId()) {
synchronized (account1) {
synchronized (account2) { /* work */ }
}
} -
Timeout istifadə edin:
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// work
} finally {
lock.unlock();
}
} else {
// Handle timeout
} -
Lock-ları minimuma endirin:
// Yaxşı - kiçik critical section
data = prepareData();
synchronized (lock) {
updateSharedState(data);
}
// Pis - böyük critical section
synchronized (lock) {
data = prepareData(); // Lock lazım deyil
updateSharedState(data);
} -
Nested locks-dan çəkinin:
// Mümkünsə, nested lock istifadə etməyin
synchronized (lock1) {
synchronized (lock2) { // Riski artırır
// work
}
} -
Higher-level concurrency utilities istifadə edin:
// synchronized əvəzinə
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
ExecutorService executor = Executors.newFixedThreadPool(4);
Deadlock Monitoring
class DeadlockMonitorService {
private final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1);
public void startMonitoring() {
scheduler.scheduleAtFixedRate(() -> {
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] ids = bean.findDeadlockedThreads();
if (ids != null) {
ThreadInfo[] infos = bean.getThreadInfo(ids, true, true);
System.err.println("DEADLOCK DETECTED!");
for (ThreadInfo info : infos) {
System.err.println("Thread: " + info.getThreadName());
System.err.println("State: " + info.getThreadState());
LockInfo lock = info.getLockInfo();
if (lock != null) {
System.err.println("Waiting for: " + lock);
}
System.err.println("Stack trace:");
for (StackTraceElement element : info.getStackTrace()) {
System.err.println(" " + element);
}
System.err.println();
}
// Alert göndər, log yaz, və s.
handleDeadlock();
}
}, 0, 10, TimeUnit.SECONDS);
}
private void handleDeadlock() {
// Email göndər, restart et, və s.
}
public void stopMonitoring() {
scheduler.shutdown();
}
}
Özət
Deadlock qarşısını almaq üçün:
- Lock ordering istifadə edin
- tryLock() və timeout istifadə edin
- Nested locks-dan çəkinin
- Higher-level concurrency utilities istifadə edin
- Deadlock monitoring əlavə edin
Deadlock baş verdikdə:
- Thread dump alın (jstack, JConsole)
- Hansı thread-lərin block olduğunu tapın
- Lock sırasını yoxlayın
- Kod dizaynını yenidən nəzərdən keçirin