Əsas məzmuna keçin

Java Race Conditions

Race Condition Nədir?

Race condition iki və ya daha çox thread eyni resursa eyni zamanda giriş etdikdə baş verir və nəticə thread-lərin icra sırasından asılı olur.

Problem Nümunəsi

Unsafe Counter

class UnsafeCounter {
private int count = 0;

public void increment() {
count++; // Bu 3 əməliyyat: read-modify-write
}

public int getCount() {
return count;
}
}

// Test
UnsafeCounter counter = new UnsafeCounter();

// 1000 thread, hər biri 1000 dəfə increment
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
}).start();
}

Thread.sleep(5000);
System.out.println("Expected: 1000000, Actual: " + counter.getCount());
// Actual < 1000000 (race condition!)

Niyə Problem?

// count++ əslində 3 addımdır:
// 1. count dəyərini oxu (read)
// 2. 1 əlavə et (modify)
// 3. yeni dəyəri yaz (write)

// İki thread eyni zamanda:
// Thread A: count oxuyur (0)
// Thread B: count oxuyur (0)
// Thread A: 0 + 1 = 1, yazır
// Thread B: 0 + 1 = 1, yazır
// Nəticə: 1 (2 olmalı idi!)

Həll Yolları

1. synchronized Keyword

class SafeCounter {
private int count = 0;

public synchronized void increment() {
count++; // İndi thread-safe
}

public synchronized int getCount() {
return count;
}
}

2. synchronized Block

class SafeCounter {
private int count = 0;
private final Object lock = new Object();

public void increment() {
synchronized(lock) {
count++;
}
}

public int getCount() {
synchronized(lock) {
return count;
}
}
}

3. Atomic Classes

import java.util.concurrent.atomic.AtomicInteger;

class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0);

public void increment() {
count.incrementAndGet(); // Thread-safe, lock-free
}

public int getCount() {
return count.get();
}
}

4. ReentrantLock

import java.util.concurrent.locks.ReentrantLock;

class LockCounter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();

public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}

public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}

Daha Mürəkkəb Nümunələr

Bank Account Transfer

class BankAccount {
private double balance;
private final Object lock = new Object();

public BankAccount(double balance) {
this.balance = balance;
}

public void withdraw(double amount) {
synchronized(lock) {
if (balance >= amount) {
balance -= amount;
}
}
}

public void deposit(double amount) {
synchronized(lock) {
balance += amount;
}
}

public double getBalance() {
synchronized(lock) {
return balance;
}
}
}

// Transfer between accounts
public static void transfer(BankAccount from, BankAccount to, double amount) {
// Deadlock potensialı var!
synchronized(from) {
synchronized(to) {
from.withdraw(amount);
to.deposit(amount);
}
}
}

Deadlock-Free Transfer

// Lock ordering ilə deadlock-dan qaçınmaq
public static void transfer(BankAccount from, BankAccount to, double amount) {
BankAccount firstLock = from.hashCode() < to.hashCode() ? from : to;
BankAccount secondLock = from.hashCode() < to.hashCode() ? to : from;

synchronized(firstLock) {
synchronized(secondLock) {
from.withdraw(amount);
to.deposit(amount);
}
}
}

Collections-da Race Conditions

Unsafe ArrayList

List<Integer> list = new ArrayList<>();

// Multiple threads adding
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
list.add(j); // Race condition!
}
}).start();
}

// Possible issues:
// 1. Data corruption
// 2. ArrayIndexOutOfBoundsException
// 3. Infinite loops

Thread-Safe Solutions

// 1. Vector (synchronized)
List<Integer> vector = new Vector<>();

// 2. Collections.synchronizedList
List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());

// 3. ConcurrentHashMap
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();

// 4. Copy-on-Write
List<Integer> cowList = new CopyOnWriteArrayList<>();

Common Race Condition Patterns

Check-Then-Act

// Unsafe
if (!map.containsKey(key)) {
map.put(key, value); // Race condition here!
}

// Safe
synchronized(map) {
if (!map.containsKey(key)) {
map.put(key, value);
}
}

// Better with ConcurrentHashMap
map.putIfAbsent(key, value);

Lazy Initialization

class Singleton {
private volatile Singleton instance;

public Singleton getInstance() {
if (instance == null) { // First check
synchronized(this) {
if (instance == null) { // Second check
instance = new Singleton(); // Double-checked locking
}
}
}
return instance;
}
}

Performance Considerations

Lock Granularity

class HashTableExample {
private final int buckets = 16;
private final Object[] locks = new Object[buckets];
private List<Entry>[] table = new List[buckets];

public HashTableExample() {
for (int i = 0; i < buckets; i++) {
locks[i] = new Object();
table[i] = new ArrayList<>();
}
}

public void put(String key, String value) {
int bucket = key.hashCode() % buckets;
synchronized(locks[bucket]) { // Fine-grained locking
table[bucket].add(new Entry(key, value));
}
}
}

Best Practices

  1. Immutable objects istifadə edin:

    public final class ImmutablePoint {
    private final int x, y;

    public ImmutablePoint(int x, int y) {
    this.x = x;
    this.y = y;
    }

    public int getX() { return x; }
    public int getY() { return y; }
    }
  2. Thread-safe collections:

    Map<String, String> map = new ConcurrentHashMap<>();
    List<String> list = new CopyOnWriteArrayList<>();
  3. Atomic operations:

    AtomicInteger counter = new AtomicInteger();
    AtomicReference<String> ref = new AtomicReference<>();
  4. Local variables istifadə edin:

    public void method() {
    int localVar = 0; // Thread-safe (stack-də)
    // ...
    }
  5. Stateless objects:

    public class MathUtils {
    public static int add(int a, int b) { // Stateless
    return a + b;
    }
    }

Debug və Test

Thread Dump

jstack <pid>  # Thread dump əldə etmək

Testing Race Conditions

@Test
public void testRaceCondition() throws InterruptedException {
Counter counter = new Counter();
int numThreads = 100;
int incrementsPerThread = 1000;

CountDownLatch latch = new CountDownLatch(numThreads);

for (int i = 0; i < numThreads; i++) {
new Thread(() -> {
for (int j = 0; j < incrementsPerThread; j++) {
counter.increment();
}
latch.countDown();
}).start();
}

latch.await();

assertEquals(numThreads * incrementsPerThread, counter.getCount());
}

Xülasə

Race condition-lar concurrent proqramlarda çox rast gəlinən problemlərdir. Düzgün synchronization, atomic operations və thread-safe data structures istifadə edərək bu problemlərin qarşısını almaq mümkündür.