javaadvanced

ThreadLocal and Scoped Values

Use ThreadLocal for per-thread data and ScopedValue (Java 21+) for structured concurrency contexts.

java
import java.util.concurrent.*;

public class ThreadLocalDemo {

    // ThreadLocal for request context
    private static final ThreadLocal<RequestContext> context = new ThreadLocal<>();

    record RequestContext(String requestId, String userId, long startTime) {}

    // Set at beginning of request processing
    static void handleRequest(String requestId, String userId) {
        context.set(new RequestContext(requestId, userId, System.currentTimeMillis()));
        try {
            serviceLayer();
        } finally {
            context.remove(); // ALWAYS clean up to prevent memory leaks
        }
    }

    static void serviceLayer() {
        RequestContext ctx = context.get();
        System.out.printf("Service [req=%s, user=%s]%n", ctx.requestId(), ctx.userId());
        repositoryLayer();
    }

    static void repositoryLayer() {
        RequestContext ctx = context.get();
        long elapsed = System.currentTimeMillis() - ctx.startTime();
        System.out.printf("Repository [req=%s, elapsed=%dms]%n", ctx.requestId(), elapsed);
    }

    // InheritableThreadLocal — passes to child threads
    private static final InheritableThreadLocal<String> traceId =
        new InheritableThreadLocal<>();

    // ThreadLocal counter (per-thread)
    private static final ThreadLocal<Integer> counter =
        ThreadLocal.withInitial(() -> 0);

    static void increment() {
        counter.set(counter.get() + 1);
        System.out.printf("Thread %s: count=%d%n",
            Thread.currentThread().getName(), counter.get());
    }

    public static void main(String[] args) throws Exception {
        // Request context demo
        ExecutorService pool = Executors.newFixedThreadPool(2);
        pool.submit(() -> handleRequest("req-001", "alice"));
        pool.submit(() -> handleRequest("req-002", "bob"));

        // Per-thread counter
        Thread t1 = new Thread(() -> { increment(); increment(); increment(); }, "T1");
        Thread t2 = new Thread(() -> { increment(); increment(); }, "T2");
        t1.start(); t2.start();
        t1.join(); t2.join();
        // T1: 1,2,3 and T2: 1,2 — independent

        // InheritableThreadLocal
        traceId.set("trace-abc");
        new Thread(() -> System.out.println("Child trace: " + traceId.get())).start();

        pool.shutdown();
        pool.awaitTermination(5, TimeUnit.SECONDS);
    }
}

Use Cases

  • Request-scoped context in web servers
  • Per-thread counters and accumulators
  • Distributed tracing context propagation

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.