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)
+
diff --git a/thread-specific-storage/etc/seq.png b/thread-specific-storage/etc/seq.png
new file mode 100644
index 000000000000..06cf0f5c4636
Binary files /dev/null and b/thread-specific-storage/etc/seq.png differ
diff --git a/thread-specific-storage/pom.xml b/thread-specific-storage/pom.xml
new file mode 100644
index 000000000000..5fd35f404e10
--- /dev/null
+++ b/thread-specific-storage/pom.xml
@@ -0,0 +1,70 @@
+
+
+
+
+ java-design-patterns
+ com.iluwatar
+ 1.26.0-SNAPSHOT
+
+ 4.0.0
+ thread-specific-storage
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+
+
+
+ com.iluwatar.threadspecificstorage.App
+
+
+
+
+
+
+
+
+
diff --git a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java
new file mode 100644
index 000000000000..499921922b6e
--- /dev/null
+++ b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java
@@ -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.
+ *
+ * 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.
+ *
+ *
Key benefits demonstrated:
+ *
+ *
+ * - Thread safety without synchronization
+ *
- No shared mutable state
+ *
- Each task maintains its own independent context
+ *
+ */
+@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.");
+ }
+}
diff --git a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/ThreadLocalContext.java b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/ThreadLocalContext.java
new file mode 100644
index 000000000000..6e50de4f6b5b
--- /dev/null
+++ b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/ThreadLocalContext.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+/**
+ * ThreadLocalContext provides a thread-specific storage mechanism. Each thread will have its own
+ * instance of user and luggage count, preventing any data sharing across threads.
+ */
+public class ThreadLocalContext {
+
+ private static final ThreadLocal user = ThreadLocal.withInitial(() -> "Unknown");
+ private static final ThreadLocal luggageCount = ThreadLocal.withInitial(() -> 0);
+
+ private ThreadLocalContext() {}
+
+ public static void setUser(String name) {
+ user.set(name);
+ }
+
+ public static String getUser() {
+ return user.get();
+ }
+
+ public static void setLuggageCount(int count) {
+ luggageCount.set(count);
+ }
+
+ public static int getLuggageCount() {
+ return luggageCount.get();
+ }
+
+ /** Clears thread-local data to prevent memory leaks. */
+ public static void clear() {
+ user.remove();
+ luggageCount.remove();
+ }
+}
diff --git a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/AppTest.java b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/AppTest.java
new file mode 100644
index 000000000000..2b4ad25fe4fb
--- /dev/null
+++ b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/AppTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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 static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class AppTest {
+
+ @Test
+ void appStartsWithoutException() {
+ assertDoesNotThrow(() -> App.main(new String[] {}));
+ }
+
+ @Test
+ void threadLocalDataIsIsolated() throws InterruptedException {
+ Thread threadA =
+ new Thread(
+ () -> {
+ ThreadLocalContext.setUser("Thread-A");
+ ThreadLocalContext.setLuggageCount(2);
+ assertEquals("Thread-A", ThreadLocalContext.getUser());
+ assertEquals(2, ThreadLocalContext.getLuggageCount());
+ ThreadLocalContext.clear();
+ });
+
+ Thread threadB =
+ new Thread(
+ () -> {
+ ThreadLocalContext.setUser("Thread-B");
+ ThreadLocalContext.setLuggageCount(5);
+ assertEquals("Thread-B", ThreadLocalContext.getUser());
+ assertEquals(5, ThreadLocalContext.getLuggageCount());
+ ThreadLocalContext.clear();
+ });
+
+ threadA.start();
+ threadB.start();
+ threadA.join();
+ threadB.join();
+ }
+}