Java之ThredLocal賞析,助力19年網際網路寒冬
ThreadLocal使用一個典型的例子就是Android的訊息機制,子執行緒傳送訊息到主執行緒,其中就使用了ThreadLocal。
先看下基本用法,特別簡單,先定義一個類
public class ThreadLocalDemo{ // 建立一個local物件 ThreadLocal<String> local = new ThreadLocal<String>; public void set(String name){ local.set(name); } public void get(){ return local.get(); } }
定義執行類:
public classtest{ public static void main(String[] args){ ThreadLocalDemo demo=ThreadLocalDemo(); // 在主執行緒中賦值 並且列印 demo.set("AAA"); System.out.println(demo.get()); // 建立一個執行緒 new Thread(new Runnable() { @Override public void run() { // 在子執行緒中賦值 並且列印 demo.set("BBB"); System.out.println(demo.get()); } }).start(); } }
執行結果:
AAA BBB Process finished with exit code 0
可以看到在主執行緒跟子執行緒的儲存的值並不一樣,原理一句話概括
ThreadLocal的值儲存在當前執行緒例項中。也就是說ThreadLocal的值存在不同的執行緒中,所以兩個值互不干擾
主執行緒執行ThreadLocal.set("AAA")時,主執行緒中有一個Map(每個執行緒都有),Map的鍵就是我們建立的ThreadLocal物件,值就是"AAA",就會存入這對鍵值對到自己這個執行緒中。同理,子執行緒也會存入的是ThreadLocal物件跟"BBB"到自己執行緒中。
主執行緒執行get()就會從主執行緒中去尋找對應的值,子執行緒執行get()就會從子執行緒中去找,所以他們取出的值不一樣。懂了啵,就是這個意思。
下面從原始碼分析,要弄懂原始碼,首先要知道一個神奇的東西
Thread.currentThread();
這是Thread的靜態方法,作用就是哪個執行緒呼叫這個方法,這個方法就返回這個執行緒物件。
比如主執行緒呼叫Thread.currentThread();那麼此方法就返回主執行緒物件。
貼出ThreadLocal的一些方法原始碼
public void set(T value) { Thread t = Thread.currentThread();// 獲取當前執行緒,哪個執行緒呼叫這個方法,t就是哪個執行緒。 ThreadLocalMap map = getMap(t);// 獲取這個執行緒的Map 下面那個方法就是原始碼 if (map != null)// 如果這個執行緒的Map不為空 就存到這個執行緒的Map中 map.set(this, value); else// 如果Map是空的 就建立Map 並且把值存到Map中 createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals;// 直接返回執行緒的Map(ThreadLocal.ThreadLocalMap) 有可能是空的 } // 建立Map就更加簡單了,直接New void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
下面看看get方法
public T get() { Thread t = Thread.currentThread();// 獲取當前執行緒物件,不要我說你都知道 ThreadLocalMap map = getMap(t);// 再獲取這個執行緒的Map(ThreadLocal.ThreadLocalMap) if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this);// 獲取值 if (e != null) {// 不等於空就獲取到了,返回吧! @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue();// 如果沒有獲取到,則返回預設值 }
下面簡單說下每個執行緒都有的Map ThreadLocal.ThreadLocalMap類
這個類是ThreadLocal的內部類,名字雖然有Map,當時內部卻是由陣列維護的。存入的時候需要一個鍵(ThreadLocal)和一個值(T泛型)(為了方便理解,本文稱之為“鍵”,“值”)。map.set()存入原理就是根據ThreadLocal物件的threadLocalHashCode物件計算存入的位置,然後把值存到陣列的這個位置。getMap取出也是一樣,接收一個ThreadLocal物件,根據這個物件的threadLocalHashCode計算出位置,然後去那個陣列的位置去找就好了。存入取出原理就是這樣。
其中存入取出的演算法以及位置衝突本文就不分析了。
看最後一個方法 setInitialValue() 這個是用於預設返回值的。如果對一個ThreadLocal沒有使用set方法就直接使用get方法,就會觸發這個方法。
原始碼:
private T setInitialValue() { T value = initialValue();// 重點,預設返回null Thread t = Thread.currentThread();// 獲取當前執行緒 ThreadLocalMap map = getMap(t);// 獲取執行緒的Map if (map != null) map.set(this, value); else createMap(t, value); return value; }
其中initialValue()放回null,下面看如果使用這個方法
public class ThreadLocalDemo{ // 建立一個local物件 重寫initialValue方法 ThreadLocal<String> local = new ThreadLocal<String>(){ @Override protected String initialValue() { return "CCC"; } }; public void set(String name){ local.set(name); } public void get(){ return local.get(); } }
這樣的話,ThreadLocal沒有使用set方法,直接呼叫get方法,就會獲取"CCC"了。不然就是null。
ThreadLocal大概就是這樣,每個執行緒直接的變數是分開的。在併發場景大量用到,希望大家拿到滿意的Offer