Stateful Goroutines in Java

In this example, we’ll explore a stateful approach using threads and synchronized methods in Java. This approach aligns with Java’s concurrency model and ensures thread-safe access to shared state.

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;

class ReadOp {
    int key;
    int value;
}

class WriteOp {
    int key;
    int value;
}

class StateManager {
    private Map<Integer, Integer> state = new HashMap<>();

    public synchronized int read(int key) {
        return state.getOrDefault(key, 0);
    }

    public synchronized void write(int key, int value) {
        state.put(key, value);
    }
}

public class StatefulThreads {
    public static void main(String[] args) throws InterruptedException {
        AtomicLong readOps = new AtomicLong();
        AtomicLong writeOps = new AtomicLong();

        StateManager stateManager = new StateManager();
        Random random = new Random();

        // Start 100 reader threads
        for (int r = 0; r < 100; r++) {
            new Thread(() -> {
                while (true) {
                    int key = random.nextInt(5);
                    stateManager.read(key);
                    readOps.incrementAndGet();
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        return;
                    }
                }
            }).start();
        }

        // Start 10 writer threads
        for (int w = 0; w < 10; w++) {
            new Thread(() -> {
                while (true) {
                    int key = random.nextInt(5);
                    int val = random.nextInt(100);
                    stateManager.write(key, val);
                    writeOps.incrementAndGet();
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        return;
                    }
                }
            }).start();
        }

        // Let the threads work for a second
        Thread.sleep(1000);

        // Capture and report the op counts
        System.out.println("readOps: " + readOps.get());
        System.out.println("writeOps: " + writeOps.get());
    }
}

In this Java example, we use a StateManager class to encapsulate the shared state and provide synchronized access methods. This ensures that only one thread can access the state at a time, preventing race conditions.

We start 100 reader threads and 10 writer threads. Each thread performs its respective operation (read or write) in a loop, incrementing an atomic counter for each operation.

The AtomicLong class is used for readOps and writeOps counters to ensure thread-safe increments without explicit locking.

After letting the threads run for a second, we print out the total number of read and write operations performed.

To run this program:

$ javac StatefulThreads.java
$ java StatefulThreads
readOps: 72105
writeOps: 7210

Running our program shows that the thread-based state management example completes about 80,000 total operations in one second.

This approach demonstrates how to manage shared state in a multi-threaded environment in Java. While it’s more verbose than the Go example using goroutines and channels, it achieves a similar result of safe concurrent access to shared state.

Remember that the exact number of operations may vary between runs due to the nature of concurrent execution and system scheduling.

For this particular case, the thread-based approach in Java is somewhat similar in complexity to the goroutine-based one in Go. The choice between different concurrency models often depends on the specific requirements of your application and the idioms of the language you’re using. Always choose the approach that makes your program’s correctness easiest to reason about and verify.