中文导读:
在本文中,我们将介绍ThreadLocal以及此类在多线程编程中的用途。
Java ThreadLocal使您能够创建只能由同一个线程读写的变量。因此,即使二个线程正在执行相同的代码,而该代码对同一个ThreadLocal变量有一个引用,二个线程也不能看到彼此的ThreadLocal变量。所以,Java ThreadLocal提供了一种简单的方法,使代码更安全。

The Java ThreadLocal class enables you to create variables that can only be read and written by the same thread. Thus, even if two threads are executing the same code, and the code has a reference to the same ThreadLocal variable, the two threads cannot see each other's ThreadLocal variables. Thus, the Java ThreadLocal class provides a simple way to make code thread-safe that would not otherwise be so.
ThreadLocal instances are typically private static fields in classes that wish to associate the state with a thread.
For example, the class below generates counters local to each thread. A new copy ThreadLocalCounter is assigned the first time it invokes ThreadLocalCounter.get() and remains unchanged on subsequent calls.
ThreadLocalCounter class
public class ThreadLocalCounter {
private int count;public ThreadLocalCounter(int count) {
this.count = count;
}public int increment() {
return ++count;
}private static final ThreadLocal<ThreadLocalCounter> threadLocal = ThreadLocal.withInitial(() -> new ThreadLocalCounter(0));public static ThreadLocalCounter get() {
return threadLocal.get();
}
}
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
It makes multi-threading easy by not sharing the state of an object across threads. Since state is not shared so you don’t need to synchronize it with other requests. Please be noted that ThreadLocal is not a substitute of synchronization, instead it isolates an object from being exposed to multiple threads. Something like this:
Servlet filter with ThreadLocal
doGet(HttpServletRequest req, HttpServletResponse resp) {
User user = getLoggedInUser(req);
StaticClass.getThreadLocal().set(user);
try {
doSomething()
doSomethingElse()
renderResponse(resp)
}
finally {
StaticClass.getThreadLocal().remove()
}
}ThreadLocal introduces hidden coupling among classes, which makes them hard to test and debug. So it should be used with care.
It is easy to abuse ThreadLocal by treating its thread confinement property as a license to use global variables or as a means of creating “hidden” method arguments.
ThreadLocal is ideal for storing objects that are not thread-safe and object sharing across threads is not required. A good example is Hibernate Session which is not threadsafe and must not be shared across threads, so we can put Session into ThreadLocal and execute the transaction. In a servlet environment, this can happen in a filter which creates a new session for each request and commit the session after the request is complete. A similar approach can be taken for the JDBC connection.
SimpleDataFormat is not a thread-safe class, so you can use ThreadLocal to keep a copy of it per thread, thus avoiding the need for synchronization. The other option could be to create a new object on each invocation which requires more resources compared to the ThreadLocal approach.
ThreadLocal is very useful in web applications, a typical pattern is to store the state of a web request in ThreadLocal (usually in a servlet filter or spring interceptor) at the very start of the processing and then access this state from any component involved in the request processing. Normally all the processing of a web request happens in a single thread. In-fact ThreadLocal is widely used in implementing application frameworks. For example, J2EE containers associate a transaction context with an executing thread for the duration of an EJB call. This is implemented using a static Thread-Local holding the transaction context.
In creating an application that requires thread-level stats collections for e.g. stress testing apps, performance monitoring app.
ThreadLocal instance can be created just like any other Java object - via the new operator. Here is an example:
private ThreadLocal threadLocal = new ThreadLocal();
This only needs to be done once per thread. Multiple threads can now get and set values inside this ThreadLocal, and each thread will only see the value it set itself.
Once a ThreadLocal has been created you can set the value to be stored in it using its set() method.
threadLocal.set("thread value");To get value from the ThreadLocal you just need to use its get() method. Here is an example:
String threadLocalValue = (String) threadLocal.get();
It is possible to remove a value from a ThreadLocal variable. You can remove a value by calling the ThreadLocal remove() method. Here is an example:
threadLocal.remove();
You can create a ThreadLocal with a generic type. Using a generic type only objects of the generic type can be set as a value on the ThreadLocal. Additionally, you do not have to typecast the value returned by get(). Here is a generic ThreadLocal example:
private ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();
Now you can only store strings in the ThreadLocal instance. Additionally, you do not need to typecast the value obtained from the ThreadLocal:
myThreadLocal.set("Hello ThreadLocal");String threadLocalValue = myThreadLocal.get();It is possible to set an initial value for a Java ThreadLocal which will get used the first time get() is called - before set() has been called with a new value. You have two options for specifying an initial value for a ThreadLocal:
Create a ThreadLocal subclass that overrides the initialValue() method.
Create a ThreadLocal with a Supplier interface implementation.
The first way to specify an initial value for a Java ThreadLocal variable is to create a subclass of ThreadLocal which overrides its initialValue() method. The easiest way to create a subclass of ThreadLocal is to simply create an anonymous subclass, right where you create the ThreadLocal variable.
Here is an example:
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
@Override protected String initialValue() {
return String.valueOf(System.currentTimeMillis());
}
};Note, that different threads will still see different initial values. Each thread will create its own initial value. Only if you return the exact same object from the initialValue() method, all threads will see the same object.
The second method for specifying an initial value for a Java ThreadLocal variable is to use its static factory method withInitial(Supplier) passing a Supplier interface implementation as a parameter. This Supplier implementation supplies the initial value for the ThreadLocal.
Here is an example:
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() {
@Override
public String get() {
return String.valueOf(System.currentTimeMillis());
}
});Since Supplier is a functional interface, it can be implemented using a Java Lambda Expression. Here is how to use it:
ThreadLocal threadLocal3 = ThreadLocal.withInitial(
() -> String.valueOf(System.currentTimeMillis()) );In some situations, you cannot use the standard ways of setting an initial value. For instance, perhaps you need some configuration information that is not available at the time you create the ThreadLocal variable. In that case, you can set the initial value lazily. Here is an example of how setting an initial value lazily on a Java ThreadLocal:
public class MyDateFormatter { private ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>(); public String format(Date date) {
SimpleDateFormat simpleDateFormat = dateFormatter();
return simpleDateFormat.format(date);
}
private SimpleDateFormat dateFormatter() {
SimpleDateFormat format = threadLocal.get();
if(format == null) {
format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
threadLocal.set(format);
}
return format;
}
}
Notice how the format() method calls the dateFormatter() method to obtain a Java SimpleDatFormat instance. If a SimpleDateFormat instance has not been set in the ThreadLocal, a new SimpleDateFormat is created and set in the ThreadLocal variable. Once a thread has set its own SimpleDateFormat in the ThreadLocal variable, the same SimpleDateFormat object is used for that thread going forward. But only for that thread. Each thread creates its own SimpleDateFormat instance, as they cannot see each others instances set on the ThreadLocal variable.
The SimpleDateFormat class is not thread safe, so multiple threads cannot use it at the same time. To solve this problem, the MyDateFormatter class above creates a SimpleDateFormat per thread, so each thread calling the format() method will use its own SimpleDateFormat instance.
If you plan to use a Java ThreadLocal from inside a task passed to a Java Thread Pool or a Java ExecutorService, keep in mind that you do not have any guarantees which thread will execute your task. However, if all you need is to make sure that each thread uses its own instance of some object, this is not a problem. Then you can use a Java ThreadLocal with a thread pool or ExecutorService just fine.
The InheritableThreadLocal class is a subclass of ThreadLocal. Instead of each thread having its own value inside a ThreadLocal, the InheritableThreadLocal grants access to values to a thread and all child threads created by that thread.