談一談不常見卻又不可少的ThreadLocal
在寫ThreadLocal以前,須要先鞏固下一點相關知識:Java內存模型及共享變量的可見性。java
內存模型中全部變量存儲在主內存中,當一個線程中要使用某個變量時,須要從主內存複製該變量到其線程內才能操做,此時線程中操做的是主內存變量的副本,操做完成後再刷回主內存。刷回的實質就是變量賦值數據結構
若是多個線程訪問同一個變量時,每一個線程都具備一個副本,操做完畢後都會刷回主內存,刷回時間存在前後,則賦值有前後,固然後者會覆蓋前者,這是形成可見性問題的次要緣由。dom
引入以上知識點後,再來講明ThreadLocal。一個線程想使用某個變量,因而從主內存複製該共享變量到線程內部中。使用完畢後想再下次再次使用該變量時,獲得的變量副本是上次使用的副本,而不是從主內存的變量再次複製過來的副本,而且不想讓其餘線程影響到該變量。這就是ThreadLocal的目的,其實現不是經過共享變量這種方式實現的,詳細內容下面介紹ide
目的很明確,可是身處JAVA內存模型中要遵循內存模型規範,下面看看JDK是如何即知足內存模型規範,又知足ThreadLocal目的。函數
知足內存模型
這點很簡單,就是你該怎麼樣還怎麼樣,仍然受你管轄,該複製就複製,該刷回就刷回,不可見仍是會形成不可見。工具
知足ThreadLocal目的
多個線程都能訪問的變量才叫共享變量,若是控制變量的訪問方式,使其餘線程線程不能訪問就能夠了。控制方式就是將線程與變量的一一對應,將該變量的訪問入口控制到只有該線程便可,JDK中的作法就是讓線程持有這個變量(綁定到線程自己)優化
線程能夠綁定變量,可是並不知道須要綁定多少個,因而將這個存儲功能仍是交給專門的數據結構—>Map。而且還專門設計了一個用來訪問這個Map的工具,這個工具就是ThreadLocal。而且這個Map的key爲ThreadLocal實例的引用地址,value存儲真正的變量。this
這樣設計就到達目的了。ThreadLocal構建時接收個泛型告訴你存儲的變量是一個對象類型。spa
ThreadLocal設計
核心Map設計
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } ... }
這裏Map的Entry被設計爲弱引用。線程
內存存儲圖示以下
弱引用WeakReference
WeakReference是Java語言規範中爲了區別直接的對象引用(程序中經過構造函數聲明出來的對象引用)而定義的另一種引用關係。WeakReference標誌性的特色是:reference實例不會影響到被應用對象的GC回收行爲(即只要對象被除WeakReference對象以外全部的對象解除引用後,該對象即可以被GC回收),只不過在被對象回收以後,reference實例想得到被應用的對象時程序會返回null。
這裏使用弱引用的緣由:ThreadLocal目的就是達到變量只能本身訪問別的線程不能訪問的目的,Map設計的key爲ThreadLocal實例的引用地址,value爲變量,當線程存在而ThreadLocal實例被回收時,Map中的value仍是存在的(緣由後續說明)導致該變量的可達性分析失敗,從而致使此階段的內存泄露。當設計爲弱引用後,若是key被銷燬了(強引用銷燬),那麼value就處於可被回收狀態,從而避免內存泄露。
臨時內存泄露圖示
Map的持有狀態
在Thread類定義中這樣定義ThreadLocalMap
public class Thread implements Runnable { ... ThreadLocal.ThreadLocalMap threadLocals = null; ... }
也就是ThreadLocalMap是線程類Thread持有的對象,一個線程持有一個ThreadLocalMap,只有線程存在,則ThreadLocalMap必存在。這也是ThreadLocal對象銷燬後,value還存在的緣由,ThreadLocalMap還被Thread強引用。只有線程銷燬時,ThreadLocalMap纔會隨之銷燬。
ThreadLocal的使用
private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); // 接收泛型
threadLocal.set(1); // 對應泛型
Integer var=threadLocal.get();
ThreadLocal底層原理
設置變量 Set
public void set(T value) { // 獲取當前線程 Thread t = Thread.currentThread(); // 從當前線程中獲取一個ThreadLocalMap實例 ThreadLocalMap map = getMap(t); // map存在則放入key,value key爲當前ThreadLocal對象引用 if (map != null) map.set(this, value); // map不存在則構建,一樣放入key,value else createMap(t, value); }
ThreadLocalMap實例的獲取就是從Thread中獲取的,也就是上面的持有狀態,拿到以後就能夠向Map結構中存儲key,value了,前面也說過了,這裏key存在的ThreadLocal實例的引用地址,value存在的變量的引用地址。一個線程能夠存儲多個ThreadLocal實例。
getMap(Thread t)
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
就是獲取當前線程持有的ThreadLocalMap
createMap(Thread t, T firstValue)
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
給當前線程初始化ThreadLocalMap實例,並set初始值。key爲ThreadLocal實例的引用地址
獲取變量Get
public T get() { Thread t = Thread.currentThread(); //① 獲取當前線程t ThreadLocalMap map = getMap(t); //②從當前線程中獲取一個ThreadLocalMap if (map != null) { //③ ThreadLocalMap不爲null則從map中獲取value ; ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue();//④ThreadLocalMap爲null則調用setInitialValue() 返回—初始化的值 }
在最後一個return以前的代碼就是從當前線程的ThreadLocalMap中經過Map的get方法獲取變量引用的過程。這是Map的基本用用法。
存在如下狀況則須要獲取初始的默認值,這是一個對外開放的功能,就是能夠指定ThreadLocal對應的變量的初始默認值,默認爲null,能夠被重寫
- ThreadLocalMap未實例化
- ThreadLocalMap已實例化,可是尚未Set變量
設置默認的初始值
protected T initialValue() { return null; } private T setInitialValue() { T value = initialValue(); //① 初始化爲null Thread t = Thread.currentThread(); //② 獲取當前線程 ThreadLocalMap map = getMap(t); //③ 從當前線程中獲取一個ThreadLocalMap if (map != null) map.set(this, value); //④ ThreadLocalMap不爲null,則存儲key=this,value=value else createMap(t, value);//⑤ ThreadLocalMap爲null,則爲當前線程建立一個ThreadLocalMap並初始化值 return value; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
代碼流程也很簡單,只是爲了功能優化了,進行的統一封裝
- ThreadLocalMap的惰性實例化
- 獲取ThreadLocalMap時須要Thread,而ThreadLocal只是訪問控制工具,因而須要打通Thread來獲取ThreadLocalMap
移除
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
主動移除是個好的處理方式,在不使用變量時應該主動移除
到這裏ThreadLocal自己的功能已經介紹完了,能夠理解爲變量訪問工具,這個變量的訪問被控制到只能當前線程有權限訪問,其餘線程無權限。
代碼示例
簡單示例
package cn.tinyice.demo.thread; import java.util.Random; import java.util.concurrent.TimeUnit; /** * ThreadLocalDemo * * @author Tinyice */ public class ThreadLocalDemo { private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); private static final Random random=new Random(); /** * 內存模型示例 */ private int mod=0; public ThreadLocalDemo(int mod) { this.mod = mod; } public int getMod() { return mod; } public void add() { mod+=1; // 每一個線程的值是不同的 threadLocal.set(get() + random.nextInt(10)); } public Integer get() { Integer integer = threadLocal.get(); return null == integer ? 0 : integer; } public static void main(String[] args) { ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo(0); for (int i = 0; i < 1000; i++) { new ThreadLocalThread(threadLocalDemo).start(); } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("------------------------------------------------"); System.out.println(String.format("threadId=%d threadLocal value=%d mod=%d", Thread.currentThread().getId(), threadLocalDemo.get(),threadLocalDemo.getMod())); } } class ThreadLocalThread extends Thread { private ThreadLocalDemo threadLocalDemo; public ThreadLocalThread( ThreadLocalDemo threadLocalDemo) { this.threadLocalDemo = threadLocalDemo; } @Override public void run() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } threadLocalDemo.add(); System.out.println(String.format("threadId=%d threadLocal value=%d", Thread.currentThread().getId(), threadLocalDemo.get())); } }
控制檯:
threadId=853 threadLocal value=3 threadId=848 threadLocal value=9 threadId=808 threadLocal value=9 threadId=121 threadLocal value=8 ------------------------------------------------ threadId=1 threadLocal value=0 mod=999 Process finished with exit code 0
mod=999 是可見性問題形成,而value值都不同則說明不一樣線程的變量值不同。
Spring示例
public abstract class TransactionSynchronizationManager { private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class); private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources"); private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations"); private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name"); private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status"); private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level"); private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active"); ... }
public final class DateTimeContextHolder { private static final ThreadLocal<DateTimeContext> dateTimeContextHolder = new NamedThreadLocal<>("DateTimeContext"); ... }