Java Thread Local Storage explained in easiest practical way
ThreadLocal is a class in java.lang package which provides a way to store and access data of any type per thread, which means each thread will have it's separate instance of the data. During execution of a thread, one class can set data in ThreadLocal and other classes further down in the execution layer can access/read the data, as long as execution in within same thread. It is important to know that ThreadLocal data set by one thread is not visible or accessible in another thread. That the core concept of ThreadLocal storage.
The way to use ThreadLocal is declare a static property of type ThreadLocal
Since the way ThreadLocal storage works, it must be used carefully. To avoid incorrect use of ThreadLocal by mistake, it is good idea to declare ThreadLocal variables in a separate class and name the class appropriate to call it out load.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.readtorakesh.threadlocal; | |
public class AppThreadLocal { | |
public static ThreadLocal<UserContext> USER_CONTEXT = new ThreadLocal<>(); | |
} |
And to read data from ThreadLocal variable
Lets see the practical use of ThreadLocal variable. Use case is in practical when we write service API, we need the UserContext (calling user's userId, name, may roles as well) available in all layer's (API, Service, Facade, DAO etc). One way to make UserContext available in all these layers is to pass UserContext as parameter in all methods starting from top to the bottom most layer, which sounds like over kill and cluttering you method signature. ThreadLocal is best fit in such situation. At the first layer we can put UserContext in a public static ThreadLocal variable, access and it in all layer's further down during execution, once execution is done, remove it from ThreadLocal.
Lets see how does the code look like to implement this.
UserContext is the class whose instance we want to store in ThreadLocal Storage
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.readtorakesh.threadlocal; | |
public class UserContext { | |
private String userId; | |
private String firstName; | |
private String lastName; | |
public UserContext(String userId, String firstName, String lastName) { | |
super(); | |
this.userId = userId; | |
this.firstName = firstName; | |
this.lastName = lastName; | |
} | |
public String getUserId() { | |
return userId; | |
} | |
public void setUserId(String userId) { | |
this.userId = userId; | |
} | |
public String getFirstName() { | |
return firstName; | |
} | |
public void setFirstName(String firstName) { | |
this.firstName = firstName; | |
} | |
public String getLastName() { | |
return lastName; | |
} | |
public void setLastName(String lastName) { | |
this.lastName = lastName; | |
} | |
@Override | |
public String toString() { | |
return "UserContext [userId=" + userId + ", firstName=" + firstName + ", lastName=" + lastName + "]"; | |
} | |
} |
The class where we defined ThreadLocal variable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.readtorakesh.threadlocal; | |
public class AppThreadLocal { | |
public static ThreadLocal<UserContext> USER_CONTEXT = new ThreadLocal<>(); | |
} |
The Thread which sets UserContext in ThreadLocal variable, calls service api (where UserContext will be read from ThreadLocal) and once done remove data from ThreadLocal variable.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.readtorakesh.threadlocal; | |
public class RequestorThread extends Thread { | |
private UserContext userContext; | |
public RequestorThread(UserContext userContext) { | |
super(userContext.getUserId()+"-Thread"); | |
this.userContext = userContext; | |
} | |
@Override | |
public void run() { | |
// Setting userContext in Thread Local here, this will be available in ServiceApi class | |
AppThreadLocal.USER_CONTEXT.set(userContext); | |
new ServiceApi().processRequest(); | |
// Very important remove thread local variable once thread execution is done | |
AppThreadLocal.USER_CONTEXT.remove(); | |
} | |
} |
Its the service class which reads UserContext from ThreadLocal and just print it. If this calls further calls Facade or DAO, UserContext can be read there in exact same way.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.readtorakesh.threadlocal; | |
public class ServiceApi { | |
public void processRequest() { | |
//Reading user context from Thread Local variable, which was set the in the calling thread | |
UserContext userContext = AppThreadLocal.USER_CONTEXT.get(); | |
System.out.println("[" + Thread.currentThread().getName() + "] : Processing request from user: " + userContext); | |
} | |
} |
Finaly the main program. It creates 2 different UserContext instances, creates two threads and pass each UserContext to them and start both threads.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.readtorakesh.threadlocal; | |
public class MainApp { | |
public static void main(String[] args) { | |
UserContext userTommy = new UserContext("tommy", "Tom", "Tom"); | |
UserContext userJerry = new UserContext("jerry", "Jeryy", "Jam"); | |
Thread t1 = new RequestorThread(userTommy); | |
Thread t2 = new RequestorThread(userJerry); | |
t1.start(); | |
t2.start(); | |
try { | |
// We want to wait for both t1 and t2 threads to finish | |
t1.join(); | |
t2.join(); | |
}catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
} | |
} |
In the output you can see that service method executed by tommy-Thread is getting Tom's UserContext and service method executed by jerry-Thread is getting Jerry's UserContext.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
----- OUTPUT ---- | |
[tommy-Thread] : Processing request from user: UserContext [userId=tommy, firstName=Tom, lastName=Tom] | |
[jerry-Thread] : Processing request from user: UserContext [userId=jerry, firstName=Jeryy, lastName=Jam] |
Note -
It is important to remove data from ThreadLocal specially in case if your code is running under a managed container like Application Server. Because containers usually implement thread pool to process request, after processing the thread is release back to thread pool and will be used to processed other upcoming requests. Which means data set in ThreadLocal variable by first request will be available to other requests processed by same thread. Which is not correct. Also if the data put into ThreadLocal is big in size, it may lead to memory leak because thread will always remain alive in the pool and data object in ThreadLocal plus any other objects referenced by it will never become eligible for garbage collection.The power comes will responsibility so always use ThreadLocal responsibly to avoid trouble.
Download Code
https://github.com/rakeshprajapati1982/thread-localIt you found this blog helpful please share. Any question or comments are always welcome.
Comments
Post a Comment