Race Condition Nedir?

Java gibi multi-thread üzerinde çalışabilen bir programlama dilinde, race condition, multi-thread’lerin aynı anda paylaşılan bir veriye eriştiğinde ve bu veriyi değiştirdiğinde ortaya çıkan bir problemdir. Race condition, beklenmeyen ve hatalı sonuçlara yol açabilen bir durumu ifade eder. Bu makalede, Java’da race condition’ın ne olduğunu ve nasıl önlenmesi gerektiğini inceleyeceğiz.

Race Condition Nedir?

Race condition, çoklu thread’lerin ortak paylaşılan bir kaynağı eş zamanlı olarak değiştirme çabası sonucu oluşan bir durumdur. Birden fazla thread aynı anda bu kaynağı okuyabilir veya değiştirebilir, bu da beklenmedik sonuçlara neden olabilir. Örneğin, iki thread aynı değişkenin değerini artırmaya çalışıyorsa ve işlem sıralamaları düzgün bir şekilde kontrol edilmezse, sonuç yanlış olabilir.

Race Condition Örnekleri

Race condition’ı daha iyi anlamak için aşağıda iki örnek sunulmuştur:

Örnek 1: Race Condition Oluşan Kod

public class RaceConditionExample {
    private static int counter = 0;

    public static void main(String[] args) {
        Runnable incrementTask = () -> {
            for (int i = 0; i < 1000; i++) {
                counter++; // Paylaşılan değişkeni artır
            }
        };

        Thread thread1 = new Thread(incrementTask);
        Thread thread2 = new Thread(incrementTask);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Sonuç: " + counter);
    }
}

Bu örnekte, iki iş parçacığı aynı counter değişkenini artırmaya çalışır. Ancak işlem sıralamaları kontrol edilmediğinden, race condition oluşur ve sonuç her çalıştırıldığında farklılık gösterebilir. Örneğin, “Sonuç” değeri 2000 olmalıdır (her iş parçacığı 1000 kez artırır), ancak race condition nedeniyle bu değer garanti edilmez. Farklı çalıştırmalarda farklı sonuçlar elde edebilirsiniz.

İlgini çekebilir:  Matrisin Tersini Alma - Java

Örnek 2: Race Condition’ı Önleyen Kod

import java.util.concurrent.atomic.AtomicInteger;

public class RaceConditionPreventionExample {
    private static AtomicInteger counter = new AtomicInteger(0);

    public static void main(String[] args) {
        Runnable incrementTask = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.incrementAndGet(); // Paylaşılan değişkeni atomik olarak artır
            }
        };

        Thread thread1 = new Thread(incrementTask);
        Thread thread2 = new Thread(incrementTask);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Sonuç: " + counter);
    }
}

Bu örnekte, AtomicInteger kullanarak race condition’ı önleyen bir kod bulunmaktadır. incrementAndGet() metodu, değişkenin atomik olarak artırılmasını sağlar. Bu nedenle “Sonuç” değeri her zaman 2000 olacaktır, çünkü her iki iş parçacığı da değişkeni güvenli bir şekilde artırır.

Race Condition’ı Önleme Yöntemleri

Race condition’ı önlemek için aşağıda belirtilen yöntemleri kullanabilirsiniz:

1. Synchronized Bloklar

Java’da synchronized anahtar kelimesi ile belirli kod bloklarını iş parçacığı güvenli hale getirebilirsiniz. Bu bloklar sadece bir iş parçacığı tarafından aynı anda çalıştırılabilir, böylece paylaşılan kaynaklara aynı anda erişim engellenir.

public class Counter {
    private int count = 0;

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

2. Volatil Değişkenler

volatile anahtar kelimesi, bir değişkenin değerini her iş parçacığının görebileceği ve güncel değerini alabileceği şekilde işaretler. Bu, değişkenin önbellekte saklanmasını önler ve her zaman en güncel değeri döndürür.

public class SharedResource {
    private volatile int sharedValue = 0;

    public void updateSharedValue(int newValue) {
        sharedValue = newValue;
    }

    public int getSharedValue() {
        return sharedValue;
    }
}

3. Java’nın Concurrent Koleksiyonları

Java, thread-safe çalışan koleksiyonlar sunar. Bu koleksiyonlar, paylaşılan verilere güvenli bir şekilde erişim sağlar ve eşzamanlı işlemleri kolaylaştırır. Örneğin, ConcurrentHashMap, çoklu iş parçacığı tarafından güvenli bir şekilde kullanılabilen bir harita koleksiyonudur.

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
        
        concurrentMap.put("Key1", 1);
        concurrentMap.put("Key2", 2);

        // Concurrent operations on the map
        int result = concurrentMap.get("Key1");
        System.out.println("Result: " + result);
    }
}

4. Atomic Sınıflar

Java, java.util.concurrent.atomic paketi içinde yer alan atomic sınıflar sağlar. Bu sınıflar, işlem sırasında atomik operasyonlar gerçekleştirmenize olanak tanır. Örneğin, AtomicInteger kullanarak integer değeri güvenli bir şekilde artırabilirsiniz.

import java.util.concurrent.atomic.AtomicInteger;

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

    public void increment() {
        count.incrementAndGet();
    }

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

Bu yöntemler, Java’da race condition’ı önlemek için kullanılan temel yaklaşımlardır. Hangi yöntemi kullanmanız gerektiği, projenizin ihtiyaçlarına ve karmaşıklığına bağlı olarak değişebilir. İş parçacığı güvenliği gerektiren kodlar yazarken, doğru yaklaşımı seçmek ve uygun önlemleri almak önemlidir.

İlgini çekebilir:  GraalVM Nedir? JVM ile Farkları ve Performans Karşılaştırması

More Reading

Post navigation