DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • How to Convert XLS to XLSX in Java
  • Recurrent Workflows With Cloud Native Dapr Jobs
  • Java Virtual Threads and Scaling
  • Java’s Next Act: Native Speed for a Cloud-Native World

Trending

  • Comparing SaaS vs. PaaS for Kafka and Flink Data Streaming
  • Scalability 101: How to Build, Measure, and Improve It
  • Stateless vs Stateful Stream Processing With Kafka Streams and Apache Flink
  • Immutable Secrets Management: A Zero-Trust Approach to Sensitive Data in Containers
  1. DZone
  2. Coding
  3. Java
  4. A Painless Introduction to Java's ThreadLocal Storage

A Painless Introduction to Java's ThreadLocal Storage

Let’s look at some best practices for using another powerful class: ThreadLocal from java.lang, which is also implemented using WeakReference.

By 
Patson Luk user avatar
Patson Luk
·
Sep. 16, 13 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
134.0K Views

Join the DZone community and get the full member experience.

Join For Free

In my last blog post we explored WeakHashMap, which is based on WeakReference in Java. Now let’s look at some best practices for using another powerful class: ThreadLocal from java.lang, which is also implemented using WeakReference.

What is ThreadLocal? A Simple Example

As its name suggests, a single instance of ThreadLocal can store different values for each thread independently. Therefore, the value stored in a ThreadLocal instance is specific (local) to the current running thread and any other code logic running on the same thread will see the same value, but not the values set on the same instance by other threads. (There are exceptions, like InheritableThreadLocal, which inherits its parent thread’s values by default.)

Let’s consider this example:

We have a TransactionManager class that provides static methods to:

  • Start a transaction with a generated ID
  • Store that ID as a static field and provide a transaction-ID-getter method to other code logic that needs to know the current transaction ID

In a single-threaded environment, TransactionManager can simply store the ID as a static field and return it as is. However, this will certainly not work in a multiple-threaded environment. Imagine if multiple threads used TransactionManager. Transaction IDs generated by each thread could overwrite each other since there is only one static instance of transaction ID. One may synchronize and block other transactions to avoid overwrites, but this would totally defeat the purpose of having multiple threads.

In order to solve this problem, ThreadLocal provides a very neat solution:

public class TransactionManager {
private static final ThreadLocal<String> context = new ThreadLocal<String>();
public static void startTransaction() {
//logic to start a transaction
//...
context.set(generatedId); 
}
public static String getTransactionId() {
return context.get();
}
public static void endTransaction() {
//logic to end a transaction
//…
context.remove();
}
}

A different thread that starts transactions via TransactionManager will get its own transaction ID stored in the context. Any logic within the same thread can call getTransactionId() later on to retrieve the value that belongs to or is local to that thread. So, problem solved!

The Internals of ThreadLocal and How it Works

Let’s drill down a little bit into ThreadLocal’s internals. ThreadLocal is implemented by having a map (a ThreadLocalMap) as a field (with a WeakReference entry) within each thread instance. (There are actually two maps. The second one is used for InheritableThreadLocal, but let’s not complicate things.) The keys of those maps are the corresponding ThreadLocals themselves. Therefore, when a set/get is called on a ThreadLocal, it looks at the current thread, finds the map, and looks up the value with “this” ThreadLocal instance.

Still confused? I certainly am. Let’s look at a real example.

  • Code running in Thread 1 calls set() on ThreadLocal instance “A” with value "123"
  • Code running in Thread 2 calls set() on ThreadLocal instance “A” with value "234"
  • Code running in Thread 1 calls set() on ThreadLocal instance “B” with value "345"

And this is the end result:

Thread 1 (the instance)'s field ThreadLocalMap (m1) has two entries:

KeyValue
ThreadLocal A"123"
ThreadLocal B"345"

Thread 2 (the instance)’s field ThreadLocalMap (m2) has one entry:

KeyValue
ThreadLocal A"234"

Now if some code logic in Thread 1 calls get() on ThreadLocal instance “A”, the ThreadLocal logic will look up the current Thread, which is the instance Thread 1, then access the field ThreadLocalMap of that Thread instance, which is m1, it can then look up the value by using m1.get(this), with “this” as ThreadLocal and the result will be “123″.

Know What to Watch Out For!

Did I hear weak references for ThreadLocal entries? Does that mean I don’t have to clean up? Well, it’s not quite that simple.

First of all, the value object put into the ThreadLocal would not purge itself (garbage collected) if there are no more strong references to it. Instead, the weak reference is done on the thread instance, which means Java garbage collection would clean up the ThreadLocal map if the thread itself is not strongly referenced elsewhere.

So now the question is: when would the thread object get garbage collected?

The answer is: it depends, but always assume the thread is long-running. Two common examples:

  • Servlets: The threads that handle servlet requests usually stay alive in the container for the lifetime of the server instance. Code logic that uses ThreadLocal might be referenced indirectly by servlets.
  • Thread pooling java.util.concurrent.Executors: Java encourages recycling threads!

A typical usage of Executor introduced in Java 1.5 is that, if ThreadLocal maps are not cleaned up properly after a transaction is done, the next TransactionProcessingTask might inherit values from another previous, unrelated task!

ExecutorService service = Executors.newFixedThreadPool(10);
service.submit(new TransactionProcessingTask());

Be careful with initialization of ThreadLocal. Below is an implementation of a counter by thread. Can you tell what is wrong in the initialization below?

public class Counter {
private static ThreadLocal<Integer> counter = new ThreadLocal<Integer>();
static {
counter.set(0);
}
public int getCountInThread() {
return counter.get();
}
//….
}

The counter would not get initialized correctly! Though the counter is declared as static, it CANNOT be initialized by having a static initializer, since the initializer only runs once when the first thread references the Counter class. When the second thread comes in, it does not run counter.set(0) on that thread, therefore counter.get() returns null instead of 0!

With this in mind, you can probably picture the consequences of not cleaning up after yourself! An operation that runs on a recycled thread might inherit the values from a previous operation on the same thread! Besides, it can also cause memory leaks, since the instance stored in ThreadLocal will never get garbage collected if the thread is alive.

As a rule of thumb, always clean up or reset your ThreadLocal after you have finished your unit of operation! Even though the current code might be simple enough to bypass the clean-ups, it might be adapted and integrated into servlets/thread pooling later on! After all, cleaning up responsibly is always appreciated both in the realms of programming and real life.


Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • How to Convert XLS to XLSX in Java
  • Recurrent Workflows With Cloud Native Dapr Jobs
  • Java Virtual Threads and Scaling
  • Java’s Next Act: Native Speed for a Cloud-Native World

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: