概述

ThreadLocal是一個用來存儲線程內部數據的類,什麼意思呢?ThreadLocal 重點突出的是Local,也就是存儲的這個數據只有對應的線程才能訪問。其他的線程是訪問不到。哪個線程存,就只能哪個線程取。那是怎麼做到的呢?

  • 以ThreadLocal對象為鍵,任意對象為值作為存儲結構;
  • 然後把這個key-value 結構存入到ThreadLocalMap的對象中;
  • 而實際上每個線程對象都有一個同樣類型的變量指向這個ThreadLocalMap對象,且是唯一的。
  • 存取都是對這個map操作,這樣就實現了線程內部存取的策略。

ThreadLocal工作類圖.png

源碼分析(JDK5)

從概述中我們能看出來,真正實現存儲數據的是這個ThreadLocalMap,那這裏先不分析其的實現,暫且把他先理解成HashMap,便於分析。

  1. 我們從set(T value)開始分析

     public void set(T value) {
         Thread t = Thread.currentThread(); //1、獲取此方法的調用者線程
         ThreadLocalMap map = getMap(t);    //2.獲取與這個線程綁定的ThreadLocalMap對象
         if (map != null)                     //3.判斷這個對象是否為空
             map.set(this, value);             //4.不為空,就存儲這個鍵值對
         else
             createMap(t, value);           //5.為空,就去創建這個map對象並存儲這個鍵值對
     }

    源碼中可以看到對這個map對象進行了唯一性的操作,只有為空的時候才會去創建。

    那再來去看一下這個createMap(Thread t, T firstValue)getMap(Thread t)

      void createMap(Thread t, T firstValue) {
         t.threadLocals = new ThreadLocalMap(this, firstValue);
     }

    把創建的這個對象賦值給了Thread對象的一個變量t.threadLocals

      ThreadLocalMap getMap(Thread t) {
         return t.threadLocals;
     }

    實際上就是獲取這個線程保存的map對象。再來看一眼這個threadLocals在Thread中的定義


    Thread中聲明了ThreadLocalMap.png
  2. 取值 get()

      public T get() {
         Thread t = Thread.currentThread();
         ThreadLocalMap map = getMap(t);
         if (map != null) { // 不為空,就從map中取出這個threadLocal為key的entry對象
             ThreadLocalMap.Entry e = map.getEntry(this);
             if (e != null) {
                 @SuppressWarnings("unchecked")
                 T result = (T)e.value;
                 return result;
             }
         }
         return setInitialValue(); //當map為null時候,去初始化這個Map並返回null
     }

    如果你對Map了解,那麼Entry的獲取就不難了。這裏姑且先這麼認為ThreadLocalMap就是hashmap以便於理解。

  3. 移除 remove()

      public void remove() {
          ThreadLocalMap m = getMap(Thread.currentThread()); //1.獲得map
          if (m != null)           //2.map不為空,從map中移除
              m.remove(this);
      }

ThreadLocal工作原理可以簡單的概況為:
每個線程都會唯一綁定一個ThreadLocalMap的對象,用TheadLocal作為這個map的鍵,來存取值。

用法

public class MyClass {

    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    public static void main(String args[]) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set(1);
                new A().get();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set(10);
                new A().get();
            }
        }).start();

    }

    static class A {
        public void get() {
            int data = threadLocal.get();
            System.out.println("A from" + Thread.currentThread().getName() + "  get data:" + data);
        }
    }

}

運行結果.png

總結

  • ThreadLocal不是用來解決對象共享訪問問題的,而主要是提供了線程保持對象的方法和避免參數傳遞的方便的對象訪問方式。
  • 使用場景,在Android中的Looper的保存就是這個ThreadLocal的運用。

      // sThreadLocal.get() will return null unless you've called prepare().
      static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
      private static void prepare(boolean quitAllowed) {
          if (sThreadLocal.get() != null) {
              throw new RuntimeException("Only one Looper may be created per thread");
          }
          sThreadLocal.set(new Looper(quitAllowed));
      }  
    
     /**
       * Return the Looper object associated with the current thread.  Returns
       * null if the calling thread is not associated with a Looper.
       */
      public static Looper myLooper() {
          return sThreadLocal.get();
      }

    將Looper這個對象保存在對應的線程中,這樣保證了一個線程對應一個Looper。

  • 那有關ThreadLocalMap內部是如何來存取數據的,關后更……