Core Java

Introduction to TransmittableThreadLocal (TTL)

Thread-local variables are a common feature in multithreaded Java programming, enabling data isolation for individual threads. However, they fall short in scenarios involving thread pools or child threads created by frameworks, as the local context is not automatically propagated. TransmittableThreadLocal (TTL), developed by Alibaba, bridges this gap, providing an elegant solution for context propagation across threads, particularly in environments using thread pools.

This article will explore how TTL works, its key features, and how to use it effectively.

1. What is TransmittableThreadLocal?

TransmittableThreadLocal extends Java’s ThreadLocal, enabling the propagation of thread-local variables to child threads, including those reused in thread pools. This feature is critical for scenarios where tasks executed in thread pools need to share a consistent context, such as user information, tenant details, or transaction IDs.

1.1 Key Features of TransmittableThreadLocal

  • Context Propagation: Ensures thread-local values are transmitted to child threads.
  • Integration with Thread Pools: Works seamlessly with Java thread pools (Executor, ExecutorService) and custom thread executors.
  • Simple API: Easy to replace existing ThreadLocal instances with TTL.

2. Basic Usage of TransmittableThreadLocal

Firstly, to include TransmittableThreadLocal in your project, add the following dependency to your pom.xml file:

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
            <version>2.14.5</version> 
        </dependency>

The following example demonstrates how TTL ensures thread-local data consistency across parent and child threads in a thread pool.

public class TransmittableThreadLocalDemo {

    // Create a TransmittableThreadLocal variable
    private static final TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

    public static void main(String[] args) {
        // Set up a thread pool wrapped with TTL Executors
        ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));

        // Set context value in the main thread
        context.set("Main Thread Context");

        // Submit a task to the thread pool
        executorService.submit(() -> {
            System.out.println("Child Thread Context: " + context.get());
        });

        // Change the context in the main thread
        context.set("Updated Main Thread Context");

        // Submit another task to verify context propagation
        executorService.submit(() -> {
            System.out.println("Child Thread Context After Update: " + context.get());
        });

        executorService.shutdown();
    }
}

Creating and Managing TTL Variables

In the above code snippet, we define a thread-local variable (context) using TransmittableThreadLocal, which extends the capabilities of the standard ThreadLocal by enabling propagation of values to child threads. This makes it useful in scenarios where thread-local state needs to be preserved and shared across multiple threads.

To ensure the context is transmitted to tasks executed in a thread pool, the thread pool is wrapped using TtlExecutors.getTtlExecutorService. This wrapper automatically handles the propagation of thread-local values, simplifying the management of context in multithreaded environments.

Setting and Propagating Context

Before submitting tasks to the thread pool, the context variable is initialized in the main thread. TTL captures this value, ensuring that it is available to child threads during task execution. When the first task runs, it retrieves the initial context value set in the main thread, such as "Main Thread Context". If the main thread updates the context, subsequent tasks will inherit the updated value, such as "Updated Main Thread Context".

Output Verification

The printed context values (shown below) in the child threads confirm that TTL successfully propagates and updates the context across threads.

Output of the Java Transmittable Thread Local Demo

3. Using TTL with Async Tasks

TTL also works with asynchronous programming. Here is how it can be used with CompletableFutures.

public class TtlAsyncExample {

    private static final TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

    public static void main(String[] args) {
        ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));

        // Set context value
        context.set("Async Context");

        CompletableFuture.runAsync(() -> {
            System.out.println("Task Context: " + context.get());
        }, executorService).thenRun(() -> {
            System.out.println("Chained Task Context: " + context.get());
        });

        executorService.shutdown();
    }
}

In this example code, TransmittableThreadLocal (TTL) ensures the propagation of context to asynchronous tasks executed by a thread pool, maintaining consistency across the entire task chain. The first task in the chain accesses the context set in the parent thread, while subsequent chained tasks seamlessly continue to read the same context value.

Output
The context value remains consistent throughout the CompletableFuture chain, demonstrating TTL’s ability to handle complex asynchronous workflows.

Task Context: Async Context
Chained Task Context: Async Context

4. Parallel Streams Example with TransmittableThreadLocal

Using parallel streams in Java can introduce challenges with thread-local variables, as parallel streams utilize multiple threads from a common thread pool. TransmittableThreadLocal ensures the propagation of context to these threads, making it an effective solution for maintaining consistency during parallel processing.

public class ParallelStreamExample {

    private static final TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

    public static void main(String[] args) {
        // Set context value in the main thread
        context.set("Parallel Stream Context");

        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // Process the stream in parallel with TTL ensuring context propagation
        numbers.parallelStream()
                .map(TtlWrappers.wrap(number -> {
                    // Access the context in the stream
                    String threadContext = context.get();
                    System.out.println("Thread: " + Thread.currentThread().getName()
                            + ", Number: " + number
                            + ", Context: " + threadContext);
                    return threadContext + " processed " + number;
                }))
                .forEach(System.out::println);
    }
}

This code example demonstrates how TransmittableThreadLocal (TTL) ensures consistent context propagation in a multithreaded environment when processing a parallel stream. The context variable, a static TransmittableThreadLocal, is set in the main thread with the value "Parallel Stream Context". A list of integers is then processed using a parallel stream. The map function wraps a lambda expression with TtlWrappers.wrap to propagate the context to worker threads executing the stream.

Within the lambda, the current thread accesses the context value and prints details of the thread, the number being processed, and the propagated context value. Finally, the processed context is returned, concatenated with the number, and printed using forEach.

Output

When executed, the program will output something like this:

Thread: main, Number: 3, Context: Parallel Stream Context
Thread: ForkJoinPool.commonPool-worker-3, Number: 5, Context: Parallel Stream Context
Thread: ForkJoinPool.commonPool-worker-5, Number: 2, Context: Parallel Stream Context
Thread: ForkJoinPool.commonPool-worker-7, Number: 1, Context: Parallel Stream Context
Parallel Stream Context processed 3
Parallel Stream Context processed 2
Parallel Stream Context processed 1
Parallel Stream Context processed 5
Thread: ForkJoinPool.commonPool-worker-5, Number: 4, Context: Parallel Stream Context
Parallel Stream Context processed 4

The output shows that each element in the list is processed by a different thread in the parallel stream. Each thread retrieves and prints the propagated value of the context, demonstrating TTL’s capability to maintain the parent thread’s state in child threads.

5. Conclusion

This article provided an overview of TransmittableThreadLocal (TTL) and its ability to propagate thread-local variables across threads in Java. By exploring examples like parallel streams and asynchronous tasks, we showcased TTL’s effectiveness in simplifying context management in multithreaded applications. In conclusion, TTL offers a reliable solution for ensuring consistent context in scenarios where traditional ThreadLocal falls short.

6. Download the Source Code

This article explores Java Transmittable Thread Local, detailing its usage, benefits, and examples for context propagation in multithreaded applications.

Download
You can download the full source code of this example here: java transmittable thread local

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button