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
-
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; }
} -
Thread-safe collections:
Map<String, String> map = new ConcurrentHashMap<>();
List<String> list = new CopyOnWriteArrayList<>(); -
Atomic operations:
AtomicInteger counter = new AtomicInteger();
AtomicReference<String> ref = new AtomicReference<>(); -
Local variables istifadə edin:
public void method() {
int localVar = 0; // Thread-safe (stack-də)
// ...
} -
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.