I'm trying to deepen my understanding of Java's threading model.
To do this, I wrote a small test where I count how many times two threads increment a shared variable.
Here's what I did:
I declared a shared static variable in my main class:
private static int globalTotalCount = 0; I started two threads:
Thread 1: incrementsthread1CountThread 2: incrementsthread2CountBoth threads also increment the shared variable
globalTotalCount.
At the end, I expect that:
thread1Count + thread2Count == globalTotalCount However, in practice, the sum of the two thread counters does NOT match the final value of globalTotalCount. (most of the time)
Here is my code:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** * Demonstrates a race condition between two threads incrementing shared counters. * The sum of thread-specific counts and the global total will often differ due to missing synchronization. */ public class RaceConditionDemo { // Shared counters (not thread-safe) private static int globalTotalCount = 0; private static int thread1Count = 0; private static int thread2Count = 0; // Flag to signal threads to stop; volatile ensures visibility between threads private volatile boolean shouldRun = true; public RaceConditionDemo() { // Create and use a thread pool for our worker threads try (ExecutorService executor = Executors.newCachedThreadPool()) { executor.execute(thread1Task); executor.execute(thread2Task); // Let threads run for 1 seconds try { Thread.sleep(1000); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } // Stop threads shouldRun = false; // Shutdown thread pool and wait for tasks to finish executor.shutdown(); try { if (!executor.awaitTermination(2, TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); } } // Print results System.out.println("Result:"); System.out.println("\tThread 1 Count:\t" + thread1Count); System.out.println("\tThread 2 Count:\t" + thread2Count); System.out.println("--------------------------------"); System.out.println("\tSum of thread counts:\t" + (thread1Count + thread2Count)); System.out.println("\tGlobal total count:\t\t" + globalTotalCount); System.out.println("--------------------------------"); int difference = thread1Count + thread2Count - globalTotalCount; if (difference != 0) { System.out.println("Difference:\t" + difference + " => Race condition detected!"); } else { System.out.println("Difference:\t" + difference + " => No race condition detected! (this time)"); } } // Worker for thread 1: increments its own and the global counter, prints its progress private final Runnable thread1Task = () -> { while (shouldRun) { globalTotalCount++; thread1Count++; System.out.println("[Thread 1]\tCall:\t" + thread1Count + "\n"); try { Thread.sleep(1); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } }; // Worker for thread 2: increments its own and the global counter, prints its progress private final Runnable thread2Task = () -> { while (shouldRun) { globalTotalCount++; thread2Count++; System.out.println("[Thread 2]\tCall:\t" + thread2Count + "\n"); try { Thread.sleep(1); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } }; public static void main(String[] args) { new RaceConditionDemo(); } }
AtomicInteger?iincoperation for incrementing integers, but that only works for local variables, where concurrency is not a concern. For fields, the compiler generates read-modify-write commands separately.iincinstruction for fields, having a single instruction does not guarantee atomicity, e.g. non-volatilelonganddoublefield access in not guaranteed to be atomic regardless of the fact that it is performed by a single bytecode instruction.