ThreadLocal使用以及原理

Posted jinshuai86

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal使用以及原理相关的知识,希望对你有一定的参考价值。

介绍

  • ThreadLocal是一个用于创建线程局部变量的类。当前线程通过ThreadLocal的set()方法设置的变量只对当前线程可见,通过get()获取设置的变量。

使用

  • 支持泛型
ThreadLocal<String> threadLocal = new ThreadLocal<>();
  • 当前线程通过ThreadLocal对象的set(value)/get()设置变量和获取设置的变量
threadLocal.set("jinshuai");
threadLocal.get();

原理

  • 每个线程Thread维护一个ThreadLocalMap<key,value>
    • key是ThreadLocal对象,value是通过set(value)设置的值
  • 当前线程调用threadLocal.set("jinshuai");
    • 首先获取当前线程所维护的ThreadLocalMap
    • 然后判断当前线程是否已经创建过这个ThreadLocalMap
      • 如果已经创建,会将ThreadLocal对象当作key,和当前线程要设置的值当作value放到ThreadLocalMap。
      • 如果没有创建,会创建并初始化一个ThreadLocalMap(类似HashMap 初始化数组长度为2的幂,设置扩充阈值...)然后同上↑
          public void set(T value) {
              // 获取当前线程对象
              Thread t = Thread.currentThread();
              // 获取线程的ThreadLocalMap
              ThreadLocalMap map = getMap(t);
              if (map != null)
                  map.set(this, value);
              else
                  createMap(t, value);
          }
          // 获取ThreadLocalMap
          ThreadLocalMap getMap(Thread t) {
              return t.threadLocals;
          }
  • 当前线程调用threadLocal.get();
    • 首先获取当前线程所维护的ThereadLocalMap
    • 然后将ThreadLocal对象作为key获取对应的Entry
      • 如果Entry不为空获取Entry的value
      • 如果Entry为空直接返回一个setInitvalue()值也就是null
          public T get() {
              // 获取当前线程对象
              Thread t = Thread.currentThread();
              // 获取当前线程对应的ThreadLocalMap
              ThreadLocalMap map = getMap(t);
              if (map != null) {
                  // 将ThreadLocal(this)作为key获取entry
                  ThreadLocalMap.Entry e = map.getEntry(this);
                  if (e != null) {
                      @SuppressWarnings("unchecked")
                      // 获取entry的value
                      T result = (T)e.value;
                      return result;
                  }
              }
              // 如果entry为空
              return setInitialValue();
          }

应用场景

  • 如果一个对象非线程安全,但又不想通过加锁的方式实现线程安全,可以通过ThreadLocal.set()对象的值,比如SimpleDataFormat不是线程安全的,此时可以每个线程设置一个SimpleDataFormat对象
    private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };

    public String formatIt(Date date)
    {
        return formatter.get().format(date);
    }
  • 当某一个变量比如User对象在多个方法中传递时,会变得比较乱此时可以通过ThreadLocal设置变量
    • 比如在Servlet中:

      doGet(HttpServletRequest req, HttpServletResponse resp) {
          User user = getLoggedInUser(req);
          doSomething(user)
          doSomethingElse(user)
          renderResponse(resp,user)
      }
    • 每个方法都需要一个user,不够优雅(咳咳...),此时可以通过设置一个ThreadLocal单例,然后set(user):

      doGet(HttpServletRequest req, HttpServletResponse resp) {
          User user = getLoggedInUser(req);
          ThreadLocalSingleInstace.getThreadLocal().set(user)
          try {
              doSomething()
              doSomethingElse()
              renderResponse(resp)
          }
          finally {
              ThreadLocalSingleInstace.getThreadLocal().remove()
          }
      }
      // 获取ThreadLocal单例
      class ThreadLocalSingleInstace {
          static private ThreadLocal threadLocal = new ThreadLocal<User>();
          static ThreadLocal<User> getThreadLocal() {
              return threadLocal;
          }
      }

注意

  • 用完以后应该调用remove()移除设定的值,防止内存泄漏
// 会移除这个Entry
threadLocal.remove();
  • 在线程池中由于线程会被复用,所以不会停止,导致每个线程的ThreadLocalMap里的key是ThreadLocal的弱引用,GC时会将其回收,但是其对应的value一直有一个强引用不会被回收造成内存泄漏。
       /**
        * The entries in this hash map extend WeakReference, using
        * its main ref field as the key (which is always a
        * ThreadLocal object).  Note that null keys (i.e. entry.get()
        * == null) mean that the key is no longer referenced, so the
        * entry can be expunged from table.  Such entries are referred to
        * as "stale entries" in the code that follows.
        */
       static class Entry extends WeakReference<ThreadLocal<?>> {
           /** The value associated with this ThreadLocal. */
           Object value;

           Entry(ThreadLocal<?> k, Object v) {
               super(k);
               value = v;
           }
       }

参考

  • https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/
  • http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/#
  • https://stackoverflow.com/questions/817856/when-and-how-should-i-use-a-threadlocal-variable
  • https://stackoverflow.com/questions/1490919/purpose-of-threadlocal




以上是关于ThreadLocal使用以及原理的主要内容,如果未能解决你的问题,请参考以下文章

聊聊ThreadLocal原理以及使用场景-JAVA 8源码

ThreadLocal的使用及原理分析

一篇文章看懂 ThreadLocal 原理,内存泄露,缺点以及线程池复用的值传递问题

Java并发编程:ThreadLocal的使用以及实现原理解析

并发——深入分析ThreadLocal的实现原理

深入剖析大厂经典面试题之ThreadLocal原理(涉及斐波拉契散列线性探测扩容以及内存泄露问题)