logo
登录 / 注册

Java并发性:ThreadLocal

头像
朱方方
20-11-02 · senior consultant manager

中文导读:

在本文中,我们将介绍ThreadLocal以及此类在多线程编程中的用途。

Java ThreadLocal使您能够创建只能由同一个线程读写的变量。因此,即使二个线程正在执行相同的代码,而该代码对同一个ThreadLocal变量有一个引用,二个线程也不能看到彼此的ThreadLocal变量。所以,Java ThreadLocal提供了一种简单的方法,使代码更安全。

Image for post

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  variable, the two threads cannot see each other's  variables. Thus, the Java ThreadLocal class provides a simple way to make code thread-safe that would not otherwise be so.

Pattern to use ThreadLocal

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 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();
   }
}

Garbage Collection Behaviour

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).

Benefits of using ThreadLocal

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  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()
  }
}

Issues with ThreadLocal

  1. ThreadLocal introduces hidden coupling among classes, which makes them hard to test and debug. So it should be used with care.

  2. 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.

Usecase

  1. 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.

  2.  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.

  3. 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.

  4. In creating an application that requires thread-level stats collections for e.g. stress testing apps, performance monitoring app.

Creating a ThreadLocal

 instance can be created just like any other Java object - via the  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 , and each thread will only see the value it set itself.

Set ThreadLocal Value

Once a  has been created you can set the value to be stored in it using its  method.

threadLocal.set("thread value");

Get ThreadLocal Value

To get value from the  you just need to use its  method. Here is an example:

String threadLocalValue = (String) threadLocal.get();

Remove ThreadLocal Value

It is possible to remove a value from a ThreadLocal variable. You can remove a value by calling the   method. Here is an example:

threadLocal.remove();

Generic ThreadLocal

You can create a  with a generic type. Using a generic type only objects of the generic type can be set as a value on the . Additionally, you do not have to typecast the value returned by . Here is a generic  example:

private ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();

Now you can only store strings in the  instance. Additionally, you do not need to typecast the value obtained from the :

myThreadLocal.set("Hello ThreadLocal");String threadLocalValue = myThreadLocal.get();

Initial ThreadLocal Value

It is possible to set an initial value for a Java  which will get used the first time  is called - before  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  method.

  • Create a ThreadLocal with a  interface implementation.

Override initialValue()

The first way to specify an initial value for a Java  variable is to create a subclass of  which overrides its  method. The easiest way to create a subclass of  is to simply create an anonymous subclass, right where you create the  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  method, all threads will see the same object.

With a Supplier Implementation

The second method for specifying an initial value for a Java  variable is to use its static factory method  passing a  interface implementation as a parameter. This  implementation supplies the initial value for the .

Here is an example:

ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() {
   @Override
   public String get() {
       return String.valueOf(System.currentTimeMillis());
   }
});

Since  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()) );

Lazy Setting of ThreadLocal Value

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  method calls the dateFormatter method to obtain a Java SimpleDatFormat instance. If a  instance has not been set in the , a new  is created and set in the  variable. Once a thread has set its own  in the  variable, the same  object is used for that thread going forward. But only for that thread. Each thread creates its own  instance, as they cannot see each others instances set on the  variable.

The  class is not thread safe, so multiple threads cannot use it at the same time. To solve this problem, the  class above creates a  per thread, so each thread calling the  method will use its own  instance.

Using a ThreadLocal with a Thread Pool or ExecutorService

If you plan to use a Java  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  just fine.

InheritableThreadLocal

The  class is a subclass of . Instead of each thread having its own value inside a , the  grants access to values to a thread and all child threads created by that thread.


Java并发性:ThreadLocal脉脉
阅读 6
声明:本文内容由脉脉用户自发贡献,部分内容可能整编自互联网,版权归原作者所有,脉脉不拥有其著作权,亦不承担相应法律责任。如果您发现有涉嫌抄袭的内容,请发邮件至maimai@taou.com,一经查实,将立刻删除涉嫌侵权内容。
头像
我来说几句...