ThreadLocal是什麼
ThreadLocal這個詞如果直接翻譯就是“本地線程”,可是如果真的按“本地線程”來理解,那就確實大錯特錯了,ThreadLocal它並不是一個Thread,它跟Thread確實有關係,是用來維護Thread的有關變量的,把它命名爲ThreadLocalVariable可能更容易讓人理解,在多線程中ThreadLocal爲變量在每個線程中都創建了一個跟特定線程有關的變量的副本,這樣就可以使每個線程在運行中只可以使用與自己線程有關的特定的副本變量,而不會影響其它線程的副本變量,保證了線程間變量的隔離性。
對於線程來說,當多個線程都用到變量時,通過ThreadLocal使每個線程都有一個本地的獨屬於自己的變量,這也是類名中“Local”所要表達的意思。
ThreadLocal例子
ThreadLocal內部其實是一個map集合,key是各自的線程,value是我們要放入的對象。我們先通過一個ThreadLocal的簡單demo先來理解一下ThreadLocal。
先來看MyThreadScopeData實體類,是對對象類型的數據封裝,讓外界不可直接操作ThreadLocal變量,我們通過單例模式得到這個實體類,這樣的話讓這個類針對不同線程分別創建一個獨立的實例對象,然後將這個實例對象作爲變量放到ThreadLocal中。
- package com.tgb.threadlocal;
- /**
- * 與ThreadLocal有關的實體對象
- * @author kang
- *
- */
- public class MyThreadScopeData {
- //聲明一個ThreacLoacl
- private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();
- //通過單例模式來獲取對象實例
- private MyThreadScopeData(){}
- public static MyThreadScopeData getThreadInstance(){
- MyThreadScopeData instance=map.get();
- if (instance ==null) {
- instance = new MyThreadScopeData();
- map.set(instance);
- }
- return instance;
- }
- private String name;
- private int age;
- public static ThreadLocal<MyThreadScopeData> getMap() {
- return map;
- }
- public static void setMap(ThreadLocal<MyThreadScopeData> map) {
- MyThreadScopeData.map = map;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- }
接下來是客戶端調用類,我們在客戶端代碼中通過製造了兩個線程,在每個線程中都生成一個隨機數,然後把這個隨機數放入到兩個不同的threadlocal對象中,然後將數據從threadlocal中取出,打印出線程名稱和取出來的數據;除了client客戶端還包含了兩個內部類,是用來從threadlocal對象中取出線程名稱和數據並打印的
- package com.tgb.threadlocal;
- import java.util.Random;
- /**
- * 測試類關於ThreadLocal
- * @author kang
- *
- */
- public class ThreadLocalTest {
- private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();
- public static void main(String[] args) {
- ThreadLocal<String> ss = new ThreadLocal<String>();
- //製造兩個線程
- for (int i = 0; i < 2; i++) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- //生成一個隨機數並打印
- int data = new Random().nextInt();
- System.out.println(Thread.currentThread().getName()
- + " has put data :" + data);
- //將隨機數放入兩個不同的ThreadLocal中
- x.set(data);
- MyThreadScopeData.getThreadInstance().setName("name" + data);
- MyThreadScopeData.getThreadInstance().setAge(data);
- //從ThreadLocal中取出數據並打印
- new A().get();
- new B().get();
- System.out.println("#########################################");
- }
- }).start();
- }
- }
- //內部類A,從兩個ThreadLocal對象中取出數據,並打印
- static class A {
- public void get() {
- //從value爲int類型的ThreadLocal中取出數據,並打印
- int data = x.get();
- System.out.println("A from " + Thread.currentThread().getName()
- + " get int :" + data);
- //從ThreadLocal實體對象中取出線程中放入的數據
- MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
- System.out
- .println("A from " + Thread.currentThread().getName()
- + " 實體對象數據: " + myData.getName() + ","
- + myData.getAge());
- }
- }
- //內部類B,從兩個ThreadLocal對象中取出數據,並打印
- static class B {
- public void get() {
- //從value爲int類型的ThreadLocal中取出數據,並打印
- int data = x.get();
- System.out.println("B from " + Thread.currentThread().getName()
- + " get int :" + data);
- //從ThreadLocal實體對象中取出線程中放入的數據
- MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
- System.out
- .println("B from " + Thread.currentThread().getName()
- + " 實體對象數據: " + myData.getName() + ","
- + myData.getAge());
- }
- }
- }
我們看一下執行的結果:
Thread-1 has putdata :-458782705
A from Thread-1 getint :-458782705
A from Thread-1實體對象數據: name-458782705,-458782705
B from Thread-1 getint :-458782705
B from Thread-1實體對象數據: name-458782705,-458782705
#########################################
Thread-0 has putdata :1881149941
A from Thread-0 getint :1881149941
A from Thread-0實體對象數據: name1881149941,1881149941
B from Thread-0 getint :1881149941
B from Thread-0實體對象數據: name1881149941,1881149941
#########################################
通過執行結果我們可以看出,在兩個線程的執行過程中生成的隨機數是不一樣的,通過將數據放入ThreadLocal中並取出打印,我們發現每個線程中的數據是一致保持一致的,這也就證明了,ThreadLocal在同一線程中實現了線程內的數據共享,不同線程間我們實現了數據的隔離性。
ThreadLocal源碼
我們現在來看下ThreadLocal背後的代碼是怎樣實現的。
先來看下ThreadLocal的常用方法:
- public T get() { }
- public void set(T value) { }
- public void remove() { }
get()方法是用來獲取ThreadLocal在當前線程中保存的變量副本,set()用來設置當前線程中變量的副本,remove()用來移除當前線程中變量的副本
首先我們先來看下ThreadLocal類是如何爲每個線程創建變量的副本的:
- /**
- * Returns the value in the current thread's copy of this
- * thread-local variable. If the variable has no value for the
- * current thread, it is first initialized to the value returned
- * by an invocation of the {@link #initialValue} method.
- *
- * @return the current thread's value of this thread-local
- */
- public T get() {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null) {
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null) {
- @SuppressWarnings("unchecked")
- T result = (T)e.value;
- return result;
- }
- }
- return setInitialValue();
- }
首先通過currentThread()獲取當前運行線程,然後通過getMap(t)方法獲取到一個map,map的類型爲ThreadLocalMap,如果map不爲空就通過當前的ThreadLocal取出map中存取的value,如果爲空就調用setInitialValue()方法創建ThreadLocalMap並經value進行返回。
我們繼續對上面的get()方法進行詳細的分析,我們接下來看下getMap(t)方法,
- /**
- * Get the map associated with a ThreadLocal. Overridden in
- * InheritableThreadLocal.
- *
- * @param t the current thread
- * @return the map
- */
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
意思就是利用currentThread()獲取當前運行線程t,然後得到t的成員變量threadLocals,threadLocals又是什麼,我們接着往下看,
- /* ThreadLocal values pertaining to this thread. This map is maintained
- * by the ThreadLocal class. */
- ThreadLocal.ThreadLocalMap threadLocals = null;
threadLocals是ThreadLocal類的一個內部類ThreadLocalMap,我們接着看內部類ThreadLocalMap的實現,
- /**
- * ThreadLocalMap is a customized hash map suitable only for
- * maintaining thread local values. No operations are exported
- * outside of the ThreadLocal class. The class is package private to
- * allow declaration of fields in class Thread. To help deal with
- * very large and long-lived usages, the hash table entries use
- * WeakReferences for keys. However, since reference queues are not
- * used, stale entries are guaranteed to be removed only when
- * the table starts running out of space.
- */
- static class ThreadLocalMap {
- /**
- * 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;
- }
- }
可以看到ThreadLocalMap的內部類Entry繼承了WeakReference,並且使用ThreadLocal作爲鍵值,利用ThreadLocal的變量作爲value,代碼跟到這裏我的目的就達到了,所以這裏ThreadLocalMap本質就是利用Entry構造了一個key、value對,其實關於ThreadLocal的核心代碼都在這個類中,感興趣的同學可以自己接着往下看。
我們get()方法裏還有setInitialValue(),是當map爲空時返回value用的,我們看下它的代碼:
- /**
- * Variant of set() to establish initialValue. Used instead
- * of set() in case user has overridden the set() method.
- *
- * @return the initial value
- */
- private T setInitialValue() {
- T value = initialValue();
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- return value;
- }
在這個方法裏通過initialValue()獲取value,獲取當前線程,如果map不爲空就對map進行賦值要注意key是當前的ThreadLocal,爲空就進行創建map與直接賦值不一樣key爲當前Thread,同一個map爲什麼不一樣,這裏就賣個關子不再介紹了,大家在往下跟層代碼看看就明白了。
我們在看setInitialValue()中的方法initialValue()如何來獲取value的
- /**
- * An extension of ThreadLocal that obtains its initial value from
- * the specified {@code Supplier}.
- */
- static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
- private final Supplier<? extends T> supplier;
- SuppliedThreadLocal(Supplier<? extends T> supplier) {
- this.supplier = Objects.requireNonNull(supplier);
- }
- @Override
- protected T initialValue() {
- return supplier.get();
- }
- }
通過ThreadLocal的內部類SuppliedThreadLocal的initialValue()方法得到泛型,並進行返回。
到這裏我們就將get()方法如何爲ThreadLocal的每個線程創建變量的副本的詳細的介紹完了,跟蹤完了以後發現確實挺簡單,在每個線程Thread內部有一個ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個threadLocals就是用來存儲實際的變量副本的,鍵值爲當前ThreadLocal變量,value爲變量副本(即T類型的變量)。