Skip to content

Commit 941eef8

Browse files
committed
thread-specific-storage
1 parent ede37bd commit 941eef8

File tree

6 files changed

+458
-0
lines changed

6 files changed

+458
-0
lines changed

thread-specific-storage/README.md

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
---
2+
title: "Thread-Specific Storage Pattern in Java: Ensuring Thread Safety without Synchronization"
3+
shortTitle: Thread-Specific Storage
4+
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."
5+
category: Concurrency
6+
language: en
7+
tag:
8+
- Concurrency
9+
- Thread Safety
10+
- Performance Optimization
11+
- Java ThreadLocal
12+
---
13+
14+
## Also known as
15+
16+
* Per-Thread Context
17+
* Thread-Local Storage
18+
19+
## Intent of Thread-Specific Storage Design Pattern
20+
21+
The Thread-Specific Storage (TSS) pattern provides each thread with its own instance of a variable, ensuring thread safety without explicit synchronization.
22+
23+
By leveraging this approach, you can prevent race conditions and avoid the performance overhead associated with locks.
24+
25+
## Detailed Explanation of Tolerant Reader Pattern with Real-World Examples
26+
27+
Real-world example
28+
29+
> 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.
30+
31+
Similarly, in multi-threaded software, each thread maintains its own copy of data using Thread-Specific Storage, ensuring isolation and preventing data corruption.
32+
33+
In plain words
34+
35+
> Each thread has its own data instance, avoiding the need for synchronization or shared state management.
36+
37+
38+
Sequence diagram
39+
40+
![Thread Specific Storage](./etc/seq.png)
41+
42+
## Programmatic Example of Thread-Specific Storage Pattern in Java
43+
44+
Let’s simulate a hotel management system where each guest (thread) has their own luggage count stored in a ThreadLocal variable.
45+
46+
```java
47+
package com.iluwatar.threadspecificstorage;
48+
49+
import java.util.concurrent.ThreadLocalRandom;
50+
51+
public class ThreadLocalContext {
52+
  private static final ThreadLocal<Integer> luggageCount = ThreadLocal.withInitial(() -> 0);
53+
54+
  public static void setLuggageCount(int count) {
55+
    luggageCount.set(count);
56+
  }
57+
58+
  public static int getLuggageCount() {
59+
    return luggageCount.get();
60+
  }
61+
62+
  public static void clear() {
63+
    // Crucial for preventing memory leaks in thread pools!
64+
    luggageCount.remove();
65+
  }
66+
}
67+
```
68+
69+
Example usage
70+
71+
```java
72+
public class App implements Runnable {
73+
74+
  @Override
75+
  public void run() {
76+
    try {
77+
      // 1. Set the initial luggage count for this thread/guest
78+
      int initialCount = ThreadLocalRandom.current().nextInt(1, 5);
79+
      ThreadLocalContext.setLuggageCount(initialCount);
80+
      System.out.printf("%s: Initial luggage count set to %d%n",
81+
          Thread.currentThread().getName(), ThreadLocalContext.getLuggageCount());
82+
83+
      // 2. Simulate some independent work
84+
      Thread.sleep(100);
85+
86+
      // 3. Update the count - this only affects this thread's copy
87+
      ThreadLocalContext.setLuggageCount(ThreadLocalContext.getLuggageCount() + 1);
88+
      System.out.printf("%s: New luggage count is %d%n",
89+
          Thread.currentThread().getName(), ThreadLocalContext.getLuggageCount());
90+
91+
    } catch (InterruptedException e) {
92+
      Thread.currentThread().interrupt();
93+
    } finally {
94+
      // 4. Cleanup is vital, especially with thread pools
95+
      ThreadLocalContext.clear();
96+
      System.out.printf("%s: Cleared luggage context.%n", Thread.currentThread().getName());
97+
    }
98+
  }
99+
100+
  public static void main(String[] args) {
101+
    // Two threads operate on their own isolated 'luggageCount'
102+
    new Thread(new App(), "Guest-Alice").start();
103+
    new Thread(new App(), "Guest-Bob").start();
104+
  }
105+
}
106+
```
107+
108+
109+
110+
Program output (Order may vary due to concurrency):
111+
112+
```
113+
Guest-Alice: Initial luggage count set to 3
114+
Guest-Bob: Initial luggage count set to 1
115+
Guest-Alice: New luggage count is 4
116+
Guest-Bob: New luggage count is 2
117+
Guest-Bob: Cleared luggage context.
118+
Guest-Alice: Cleared luggage context.
119+
```
120+
121+
## When to Use the Tolerant Reader Pattern in Java
122+
123+
* 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.
124+
* 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.
125+
* 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.
126+
127+
## Real-World Applications of Tolerant Reader Pattern in Java
128+
129+
* Java's ThreadLocal class is the direct implementation.
130+
* Database Transaction Management in frameworks like Spring, where the current transaction object is stored in a ThreadLocal.
131+
* 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.
132+
133+
## Benefits and Trade-offs of Tolerant Reader Pattern
134+
135+
Benefits:
136+
137+
* Superior Performance: Eliminates the overhead of explicit locking and synchronization primitives.
138+
* Simplified Thread Safety: Naturally prevents race conditions by isolating state.
139+
* Cleaner API: Avoids cluttering method signatures by not requiring context parameters to be passed through multiple layers.
140+
141+
Trade-offs:
142+
143+
* 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.
144+
* Increased Code Obscurity: Hides the state management, making it less obvious that a variable is thread-specific and not a shared global one.
145+
146+
## Related Java Design Patterns
147+
148+
* [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.
149+
* [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.
150+
* [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.
151+
152+
## References and Credits
153+
154+
* [Seminal pattern catalog that documents TSS as a concurrency pattern.](https://www.dre.vanderbilt.edu/~schmidt/POSA/POSA2/conc-patterns.html)
155+
* [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/)
156+
* [Comprehensive paper defining the original TSS pattern and its benefits in eliminating locking overhead.](https://www.dre.vanderbilt.edu/~schmidt/PDF/TSS-pattern.pdf)
157+
805 KB
Loading

thread-specific-storage/pom.xml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
5+
6+
The MIT License
7+
Copyright © 2014-2022 Ilkka Seppälä
8+
9+
Permission is hereby granted, free of charge, to any person obtaining a copy
10+
of this software and associated documentation files (the "Software"), to deal
11+
in the Software without restriction, including without limitation the rights
12+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
copies of the Software, and to permit persons to whom the Software is
14+
furnished to do so, subject to the following conditions:
15+
16+
The above copyright notice and this permission notice shall be included in
17+
all copies or substantial portions of the Software.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.
26+
27+
-->
28+
<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">
29+
<parent>
30+
<artifactId>java-design-patterns</artifactId>
31+
<groupId>com.iluwatar</groupId>
32+
<version>1.26.0-SNAPSHOT</version>
33+
</parent>
34+
<modelVersion>4.0.0</modelVersion>
35+
<artifactId>thread-specific-storage</artifactId>
36+
<dependencies>
37+
<dependency>
38+
<groupId>org.slf4j</groupId>
39+
<artifactId>slf4j-api</artifactId>
40+
</dependency>
41+
<dependency>
42+
<groupId>ch.qos.logback</groupId>
43+
<artifactId>logback-classic</artifactId>
44+
</dependency>
45+
<dependency>
46+
<groupId>org.junit.jupiter</groupId>
47+
<artifactId>junit-jupiter-engine</artifactId>
48+
<scope>test</scope>
49+
</dependency>
50+
</dependencies>
51+
<build>
52+
<plugins>
53+
<plugin>
54+
<groupId>org.apache.maven.plugins</groupId>
55+
<artifactId>maven-assembly-plugin</artifactId>
56+
<executions>
57+
<execution>
58+
<configuration>
59+
<archive>
60+
<manifest>
61+
<mainClass>com.iluwatar.threadspecificstorage.App</mainClass>
62+
</manifest>
63+
</archive>
64+
</configuration>
65+
</execution>
66+
</executions>
67+
</plugin>
68+
</plugins>
69+
</build>
70+
</project>
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
3+
*
4+
* The MIT License
5+
* Copyright © 2014-2022 Ilkka Seppälä
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
package com.iluwatar.threadspecificstorage;
26+
27+
import lombok.extern.slf4j.Slf4j;
28+
29+
/**
30+
* The Thread-Specific Storage pattern ensures that each thread has its own instance of a variable.
31+
* This is useful when multiple threads access the same code but must not share the same state or
32+
* data.
33+
*
34+
* <p>In this example, each hotel guest has a personal luggage record. Each guest (represented by a
35+
* thread) updates their personal luggage count. The values are stored in a ThreadLocal variable
36+
* ensuring thread isolation.
37+
*
38+
* <p>Key benefits demonstrated:
39+
*
40+
* <ul>
41+
* <li>Thread safety without synchronization
42+
* <li>No shared mutable state
43+
* <li>Each task maintains its own independent context
44+
* </ul>
45+
*/
46+
@Slf4j
47+
public class App {
48+
49+
/**
50+
* Program main entry point.
51+
*
52+
* @param args program runtime arguments
53+
*/
54+
public static void main(String[] args) {
55+
56+
LOGGER.info("Hotel system starting using Thread-Specific Storage!");
57+
LOGGER.info("Each guest has a separate luggage count stored thread-locally.");
58+
59+
Thread guest1 =
60+
new Thread(
61+
() -> {
62+
ThreadLocalContext.setUser("Guest-A");
63+
ThreadLocalContext.setLuggageCount(2);
64+
LOGGER.info(
65+
"{} starts with {} luggage items.",
66+
ThreadLocalContext.getUser(),
67+
ThreadLocalContext.getLuggageCount());
68+
ThreadLocalContext.setLuggageCount(5);
69+
LOGGER.info(
70+
"{} updated luggage count to {}.",
71+
ThreadLocalContext.getUser(),
72+
ThreadLocalContext.getLuggageCount());
73+
ThreadLocalContext.clear();
74+
});
75+
76+
Thread guest2 =
77+
new Thread(
78+
() -> {
79+
ThreadLocalContext.setUser("Guest-B");
80+
ThreadLocalContext.setLuggageCount(1);
81+
LOGGER.info(
82+
"{} starts with {} luggage items.",
83+
ThreadLocalContext.getUser(),
84+
ThreadLocalContext.getLuggageCount());
85+
ThreadLocalContext.setLuggageCount(3);
86+
LOGGER.info(
87+
"{} updated luggage count to {}.",
88+
ThreadLocalContext.getUser(),
89+
ThreadLocalContext.getLuggageCount());
90+
ThreadLocalContext.clear();
91+
});
92+
93+
guest1.start();
94+
guest2.start();
95+
96+
try {
97+
guest1.join();
98+
guest2.join();
99+
} catch (InterruptedException e) {
100+
LOGGER.error("Thread interrupted", e);
101+
}
102+
103+
LOGGER.info("All guest operations finished. System shutting down.");
104+
}
105+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
3+
*
4+
* The MIT License
5+
* Copyright © 2014-2022 Ilkka Seppälä
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
package com.iluwatar.threadspecificstorage;
26+
27+
/**
28+
* ThreadLocalContext provides a thread-specific storage mechanism. Each thread will have its own
29+
* instance of user and luggage count, preventing any data sharing across threads.
30+
*/
31+
public class ThreadLocalContext {
32+
33+
private static final ThreadLocal<String> user = ThreadLocal.withInitial(() -> "Unknown");
34+
private static final ThreadLocal<Integer> luggageCount = ThreadLocal.withInitial(() -> 0);
35+
36+
private ThreadLocalContext() {}
37+
38+
public static void setUser(String name) {
39+
user.set(name);
40+
}
41+
42+
public static String getUser() {
43+
return user.get();
44+
}
45+
46+
public static void setLuggageCount(int count) {
47+
luggageCount.set(count);
48+
}
49+
50+
public static int getLuggageCount() {
51+
return luggageCount.get();
52+
}
53+
54+
/** Clears thread-local data to prevent memory leaks. */
55+
public static void clear() {
56+
user.remove();
57+
luggageCount.remove();
58+
}
59+
}

0 commit comments

Comments
 (0)