Əsas məzmuna keçin

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:

  1. Mutual Exclusion - Resurs yalnız bir thread tərəfindən istifadə edilə bilər
  2. Hold and Wait - Thread resurs tutaraq digərini gözləyir
  3. No Preemption - Resurs zorla alına bilməz
  4. 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

  1. Lock ordering istifadə edin:

    // Həmişə eyni sıra ilə lock alın
    if (account1.getId() < account2.getId()) {
    synchronized (account1) {
    synchronized (account2) { /* work */ }
    }
    }
  2. Timeout istifadə edin:

    if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
    // work
    } finally {
    lock.unlock();
    }
    } else {
    // Handle timeout
    }
  3. 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);
    }
  4. Nested locks-dan çəkinin:

    // Mümkünsə, nested lock istifadə etməyin
    synchronized (lock1) {
    synchronized (lock2) { // Riski artırır
    // work
    }
    }
  5. 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