diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml index cac1250b70e8..109934053914 100644 --- a/.github/workflows/presubmit.yml +++ b/.github/workflows/presubmit.yml @@ -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" diff --git a/pom.xml b/pom.xml index 8337c97966da..698f14867e7a 100644 --- a/pom.xml +++ b/pom.xml @@ -232,6 +232,7 @@ template-method templateview thread-pool-executor + thread-specific-storage throttling tolerant-reader trampoline diff --git a/thread-specific-storage/README.md b/thread-specific-storage/README.md new file mode 100644 index 000000000000..f4796ec9cd44 --- /dev/null +++ b/thread-specific-storage/README.md @@ -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 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: + * + *

+ */ +@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(); + } +}