Thread-local variables = Each thread that accesses such a variable gets its own, independent copy.
No thread can see or modify another thread’s value.
Perfect for keeping thread-specific data without synchronization.
The java.lang.Thread class has hidden fields (from OpenJDK source):
// Holds all ThreadLocal values for that thread
ThreadLocal.ThreadLocalMap threadLocals = null;
// Holds inheritable values
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
ThreadLocalMap = A specialized hash map
Key: a weak reference to a ThreadLocal object (so it can be GC’d if not used).
Value: the actual thread-local value.
InheritableThreadLocal Logic:
When you start a new thread:
The JVM checks the parent thread’s inheritableThreadLocals
If any exist, they’re shallow-copied into the child’s own map.
public class ThreadLocalExample {
private static final ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
public static void main(String[] args) {
Runnable task = () -> {
threadLocalValue.set((int) (Math.random() * 100));
System.out.println(Thread.currentThread().getName() +
" → " + threadLocalValue.get());
};
Thread t1 = new Thread(task, "Thread-A");
Thread t2 = new Thread(task, "Thread-B");
t1.start();
t2.start();
}
}
Output:
Thread-A → 42
Thread-B → 87
Each thread has its own random value — even though they share the same ThreadLocal variable.
Member variable (MyThread.localVal) in a Thread subclass only works inside that specific thread class you define.
ThreadLocal works when you don't control the thread class, but still need per-thread data — e.g., in thread pools, executors, or frameworks.
ThreadLocal is not about “having local variables” inside a thread — it’s about attaching thread-specific data to shared objects.
class MyService {
private static final ThreadLocal<Integer> val = new ThreadLocal<>();
void set(int x) { val.set(x); }
int get() { return val.get(); }
}
MyService s = new MyService();
new Thread(() -> { s.set(1); System.out.println(s.get()); }).start();
new Thread(() -> { s.set(2); System.out.println(s.get()); }).start();
Database connection or session per thread: Using ThreadLocal avoids passing connections explicitly across methods
User context in web requests: Each request handled by a separate thread stores user/session data safely
DateFormat / SimpleDateFormat: These are not thread-safe (the parsing positions get tangled by different threads; synchronized lock can be used); ThreadLocal gives each thread its own instance
private static final ThreadLocal<DateFormat> df =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
Transaction management: Keeps transaction state isolated between threads
ThreadLocal variables can cause memory leaks if not cleaned up properly:
Threads in thread pools may outlive your intended scope.
Their ThreadLocalMap entries may hold strong references.
👉 Always call: threadLocal.remove(); when the value is no longer needed.
ThreadLocal<T> — basic form.
InheritableThreadLocal<T> — value is inherited by child threads created from the parent.
Example:
InheritableThreadLocal<String> context = new InheritableThreadLocal<>();
context.set("parent-value");
new Thread(() -> System.out.println(context.get())).start(); // prints "parent-value"
The java.util.Random uses a single seed and demands synchronized access to its internal state → Performance issue
Random random = new Random();
int value = random.nextInt();
ThreadLocalRandom solves this by giving each thread its own random generator:
Each thread has a separate seed (maintained in a ThreadLocal variable).
int value = ThreadLocalRandom.current().nextInt(100);
👉 Don’t create instances manually — use ThreadLocalRandom.current().
Works great in parallel streams or ForkJoinPool tasks.
👉 It’s NOT cryptographically secure (use SecureRandom for that).