Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/presubmit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_MODEL: "gemini-1.5-flash"
LLM_MODEL: "gemini-2.5-flash-preview-09-2025"
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@
<module>template-method</module>
<module>templateview</module>
<module>thread-pool-executor</module>
<module>thread-specific-storage</module>
<module>throttling</module>
<module>tolerant-reader</module>
<module>trampoline</module>
Expand Down
157 changes: 157 additions & 0 deletions thread-specific-storage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
---
title: "Thread-Specific Storage Pattern in Java: Ensuring Thread Safety without Synchronization"
shortTitle: Thread-Specific Storage
description: "Learn how the Thread-Specific Storage pattern in Java isolates data per thread using ThreadLocal, eliminating synchronization issues while maintaining performance and clarity in concurrent applications."
category: Concurrency
language: en
tag:
- Concurrency
- Thread Safety
- Performance Optimization
- Java ThreadLocal
---

## Also known as

* Per-Thread Context
* Thread-Local Storage

## Intent of Thread-Specific Storage Design Pattern

The Thread-Specific Storage (TSS) pattern provides each thread with its own instance of a variable, ensuring thread safety without explicit synchronization.

By leveraging this approach, you can prevent race conditions and avoid the performance overhead associated with locks.

## Detailed Explanation of Tolerant Reader Pattern with Real-World Examples

Real-world example

> Imagine a hotel system where each guest keeps their own luggage count. Guests (threads) operate independently — adding or removing their luggage — without interfering with others.

Similarly, in multi-threaded software, each thread maintains its own copy of data using Thread-Specific Storage, ensuring isolation and preventing data corruption.

In plain words

> Each thread has its own data instance, avoiding the need for synchronization or shared state management.


Sequence diagram

![Thread Specific Storage](./etc/seq.png)

## Programmatic Example of Thread-Specific Storage Pattern in Java

Let’s simulate a hotel management system where each guest (thread) has their own luggage count stored in a ThreadLocal variable.

```java
package com.iluwatar.threadspecificstorage;

import java.util.concurrent.ThreadLocalRandom;

public class ThreadLocalContext {
  private static final ThreadLocal<Integer> luggageCount = ThreadLocal.withInitial(() -> 0);

  public static void setLuggageCount(int count) {
    luggageCount.set(count);
  }

  public static int getLuggageCount() {
    return luggageCount.get();
  }

  public static void clear() {
    // Crucial for preventing memory leaks in thread pools!
    luggageCount.remove();
  }
}
```

Example usage

```java
public class App implements Runnable {

  @Override
  public void run() {
    try {
      // 1. Set the initial luggage count for this thread/guest
      int initialCount = ThreadLocalRandom.current().nextInt(1, 5);
      ThreadLocalContext.setLuggageCount(initialCount);
      System.out.printf("%s: Initial luggage count set to %d%n",
          Thread.currentThread().getName(), ThreadLocalContext.getLuggageCount());

      // 2. Simulate some independent work
      Thread.sleep(100);

      // 3. Update the count - this only affects this thread's copy
      ThreadLocalContext.setLuggageCount(ThreadLocalContext.getLuggageCount() + 1);
      System.out.printf("%s: New luggage count is %d%n",
          Thread.currentThread().getName(), ThreadLocalContext.getLuggageCount());

    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    } finally {
      // 4. Cleanup is vital, especially with thread pools
      ThreadLocalContext.clear();
      System.out.printf("%s: Cleared luggage context.%n", Thread.currentThread().getName());
    }
  }

  public static void main(String[] args) {
    // Two threads operate on their own isolated 'luggageCount'
    new Thread(new App(), "Guest-Alice").start();
    new Thread(new App(), "Guest-Bob").start();
  }
}
```



Program output (Order may vary due to concurrency):

```
Guest-Alice: Initial luggage count set to 3
Guest-Bob: Initial luggage count set to 1
Guest-Alice: New luggage count is 4
Guest-Bob: New luggage count is 2
Guest-Bob: Cleared luggage context.
Guest-Alice: Cleared luggage context.
```

## When to Use the Tolerant Reader Pattern in Java

* Context Management: When you need to maintain a per-request or per-thread context (e.g., user session, transaction ID, security credentials) that must be accessible by multiple classes within the same thread, without passing it explicitly as a method argument.
* Thread-Local Accumulation: When performing an accumulation or calculation in parallel where each thread needs a temporary, isolated variable (e.g., a counter or buffer) before a final, synchronized merge.
* Stateless Services with Stateful Data: In frameworks like Spring, to make a conceptually "stateful" resource (like a database connection) thread-safe by having each thread get its own copy from a pool.

## Real-World Applications of Tolerant Reader Pattern in Java

* Java's ThreadLocal class is the direct implementation.
* Database Transaction Management in frameworks like Spring, where the current transaction object is stored in a ThreadLocal.
* Log Correlation IDs where a unique ID for a request is stored at the beginning of thread execution and retrieved by logging components throughout the request processing chain.

## Benefits and Trade-offs of Tolerant Reader Pattern

Benefits:

* Superior Performance: Eliminates the overhead of explicit locking and synchronization primitives.
* Simplified Thread Safety: Naturally prevents race conditions by isolating state.
* Cleaner API: Avoids cluttering method signatures by not requiring context parameters to be passed through multiple layers.

Trade-offs:

* Potential Memory Leaks: If used with thread pools, failing to call ThreadLocal.remove() can cause the thread's local data to persist and leak memory.
* Increased Code Obscurity: Hides the state management, making it less obvious that a variable is thread-specific and not a shared global one.

## Related Java Design Patterns

* [Monitor Object:](https://en.wikipedia.org/wiki/Monitor_(synchronization)): Ensures only one thread can execute a critical section of code within an object at a time, which is the synchronization approach that TSS avoids.
* [Active Object:](https://en.wikipedia.org/wiki/Active_object): Decouples method invocation from execution, often running methods in their own thread; TSS can manage context within that dedicated thread.
* [Thread Pool:](https://en.wikipedia.org/wiki/Thread_pool): Manages a group of reusable worker threads; proper use of TSS requires cleanup (remove()) to prevent state leakage between tasks.

## References and Credits

* [Seminal pattern catalog that documents TSS as a concurrency pattern.](https://www.dre.vanderbilt.edu/~schmidt/POSA/POSA2/conc-patterns.html)
* [Doug Lea's definitive work covering the principles and patterns, including Java's ThreadLocal implementation.](https://www.oreilly.com/library/view/concurrent-programming-in/0201310090/)
* [Comprehensive paper defining the original TSS pattern and its benefits in eliminating locking overhead.](https://www.dre.vanderbilt.edu/~schmidt/PDF/TSS-pattern.pdf)

Binary file added thread-specific-storage/etc/seq.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 70 additions & 0 deletions thread-specific-storage/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).

The MIT License
Copyright © 2014-2022 Ilkka Seppälä

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>thread-specific-storage</artifactId>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.iluwatar.threadspecificstorage.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.threadspecificstorage;

import lombok.extern.slf4j.Slf4j;

/**
* The Thread-Specific Storage pattern ensures that each thread has its own instance of a variable.
* This is useful when multiple threads access the same code but must not share the same state or
* data.
*
* <p>In this example, each hotel guest has a personal luggage record. Each guest (represented by a
* thread) updates their personal luggage count. The values are stored in a ThreadLocal variable
* ensuring thread isolation.
*
* <p>Key benefits demonstrated:
*
* <ul>
* <li>Thread safety without synchronization
* <li>No shared mutable state
* <li>Each task maintains its own independent context
* </ul>
*/
@Slf4j
public class App {

/**
* Program main entry point.
*
* @param args program runtime arguments
*/
public static void main(String[] args) {

LOGGER.info("Hotel system starting using Thread-Specific Storage!");
LOGGER.info("Each guest has a separate luggage count stored thread-locally.");

Thread guest1 =
new Thread(
() -> {
ThreadLocalContext.setUser("Guest-A");
ThreadLocalContext.setLuggageCount(2);
LOGGER.info(
"{} starts with {} luggage items.",
ThreadLocalContext.getUser(),
ThreadLocalContext.getLuggageCount());
ThreadLocalContext.setLuggageCount(5);
LOGGER.info(
"{} updated luggage count to {}.",
ThreadLocalContext.getUser(),
ThreadLocalContext.getLuggageCount());
ThreadLocalContext.clear();
});

Thread guest2 =
new Thread(
() -> {
ThreadLocalContext.setUser("Guest-B");
ThreadLocalContext.setLuggageCount(1);
LOGGER.info(
"{} starts with {} luggage items.",
ThreadLocalContext.getUser(),
ThreadLocalContext.getLuggageCount());
ThreadLocalContext.setLuggageCount(3);
LOGGER.info(
"{} updated luggage count to {}.",
ThreadLocalContext.getUser(),
ThreadLocalContext.getLuggageCount());
ThreadLocalContext.clear();
});

guest1.start();
guest2.start();

try {
guest1.join();
guest2.join();
} catch (InterruptedException e) {
LOGGER.error("Thread interrupted", e);
}

LOGGER.info("All guest operations finished. System shutting down.");
}
}
Loading
Loading