1. 程式人生 > >ThreadLocal使用以及原理

ThreadLocal使用以及原理

read oci purpose 應用 eth ati ria ini mem

介紹

  • 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使用以及原理