下面咱們看一下ThreadLocal類的官方註釋。java
This class provides thread-local variables. These variables differ from數據庫
their normal counterparts in that each thread that accesses one (via its安全
{@code get} or {@code set} method) has its own, independently initialized多線程
copy of the variable. {@code ThreadLocal} instances are typically private框架
static fields in classes that wish to associate state with a thread (e.g.,socket
a user ID or Transaction ID).ide
大體的意思是,ThreadLocal提供本地線程變量。這個變量裏面的值(經過get方法獲取)是和其餘線程分割開來的,變量的值只有當前線程能訪問到,不像通常的類型好比Person,Student類型的變量,只要訪問到聲明該變量的對象,便可訪問其所有內容,並且各個線程的訪問的數據是無差異的。Thread的典型應用是提供一個與程序運行狀態相關靜態變量,好比一次訪問回話的表示符號:USERID,或者一次事務裏面的事務id:Transaction ID。學習
線程本地變量是和線程相關的變量,一個線程則一份數據。咱們經過ThreadLocal保存的數據最終是保存在Thread類的ThreadLocalMap threadLocals變量中。ThreadlocalMap是一個Map結構,其中key爲咱們聲明的ThreadLocal對象,value即爲咱們使用ThreadLocal保存的線程本地變量. 測試
當咱們調用ThreadLocal變量set方法時,那麼爲將TheadLocal做爲key,set方法的參數作爲value保存在當前線程的threadLocals中.調用get方法時相似,調用get方法時,會去Thread的threadLocals中去尋找key爲ThreadLocal 變量的值this
源碼以下:
//Thread.threadLocals變量聲明 /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; // ThreadLocal set get方法 /** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t);// getMap方法即去獲取當前線程的ThreadLocalMap變量。 if (map != null) map.set(this, value);//以this(ThreadLocal自己)爲Key,參數value爲值進行保存 else createMap(t, value); } /** * 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; } /** * 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(); }
下面是測試代碼:
static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>(); @Test public void test01(){ Thread thread1 = new Thread(){ @Override public void run() { stringThreadLocal.set("threadName===>"+Thread.currentThread().getName()); System.out.println(this.getName()+" thread get the value:"+stringThreadLocal.get()); } }; Thread thread2 = new Thread(){ @Override public void run() { stringThreadLocal.set("threadName===>"+Thread.currentThread().getName()); System.out.println(this.getName()+" thread get the value:"+stringThreadLocal.get()); } }; Thread thread3 = new Thread(){ @Override public void run() { stringThreadLocal.set("threadName===>"+Thread.currentThread().getName()); System.out.println(this.getName()+" thread get the value:"+stringThreadLocal.get()); } }; thread1.start(); thread2.start(); thread3.start(); System.out.println("main線程調用set方法以前:"+stringThreadLocal.get()); stringThreadLocal.set("main 線程set的值"); System.out.println("main線程調用set方法以後:"+stringThreadLocal.get()); }
能夠看到不一樣線程設置的值在該線程是可以正確的取到。因爲Thread的threadLocals變量只能在Thread所在的包下才可以訪問,所以不能對該變量進行直接訪問以驗證設置的值在Thread.currentThread對象裏面。但若是你調試以上代碼,設置值以後訪問Thread.currentThread.threadLocals會看到以前設置的值。其中key爲聲明的ThreadLocal對象。
這算是比較正統的ThreadLocal用法,這可能也是ThreadLocal設計的初衷:用來保存於狀態相關的變量,好比訪問者的用戶信息,事務的事務標識。這裏演示一下使用ThreadLocal來傳遞用戶信息,實際上當前流行的大部分權限框架都是使用的ThreadLocal變量來保存用戶信息的。
下面是測試代碼:
//參數傳遞測試 @Test public void test02(){ //參數主要利用ThreadLocal是線程的局部變量,只要在同一個線程中,以前設置的值後面就能取到,從而達到參數值傳遞的效果。 //在前面在線程變量中添加值 stringThreadLocal.set("name"); paramThreadLocal.set(new HashMap(){ { put("id","1"); put("name","xiaoTOT"); put("gender","M"); } }); testParam(); } private void testParam() { //從線程本地變量獲取參數 Map map = paramThreadLocal.get(); map.forEach((key,value)->{ System.out.println("key:"+key+" & value="+value); }); }
單例模式的好處無用質疑,能夠減小對象的建立。對於那些建立很是費時的對象尤爲明顯。而且若是可以用單例解決的問題經歷使用單例解決,這樣能減輕運行時的壓力。
對於一個對象假若沒有成員變量,單例很是簡單,不用去擔憂多線程同時對成員變量修改而產生的線程安全問題。
對於一個擁有成員變量的對象使用單例就須要考慮到線程安全問題。多線程訪問又能夠分爲下面兩個方面:
a:成員變量須要多線程同步,好比帳戶對象(ACCOUNT)中的成員變量餘額(amount).amount成員變量須要在多線程的訪問下保證各個線程保證絕對的同步,即不管何時線程內的值都是同樣。咱們能夠經過加同步關鍵字synchronized,volatile來解決。
b,成員變量不須要線程同步,每一個線程訪問本身線程內部的對象。好比一個服務類對數據庫的連接成員變量,每一個線程分配一個鏈接便可。相似這種場景,咱們最簡單的方式是使用多例模式來解決。單更好的方式是經過threadLocal來解決。
下面是使用ThreadLocal改造單例模式的示例:
//ThreadLocal在單例模式改造的運用 @Test public void test03(){ //單例模式的好處無用質疑,能夠減小對象的建立。對於那些建立很是費時的對象尤爲明顯。而且若是可以用單例解決的問題經歷使用單例解決,這樣能減輕運行時的壓力。 //1,對於一個對象假若沒有成員變量,單例很是簡單,不用去擔憂多線程同時對成員變量修改而產生的線程安全問題。 //2,對於一個擁有成員變量的對象使用單例就須要考慮到線程安全問題。多線程訪問又能夠分爲下面兩個方面: // a:成員變量須要多線程同步,好比帳戶對象(ACCOUNT)中的成員變量餘額(amount).amount成員變量須要在多線程的訪問下保證各個線程保證絕對的同步,即不管何時線程內的值都是同樣。 // 咱們能夠經過加同步關鍵字synchronized,volatile來解決。 // b,成員變量不須要線程同步,每一個線程訪問本身線程內部的對象。好比一個服務類對數據庫的連接成員變量,每一個線程分配一個鏈接便可。相似這種場景,咱們最簡單的方式是使用多例模式來解決。 //單更好的方式是經過threadLocal來解決。 //多例模式 Thread thread = new Thread(){ @Override public void run() { DBConnect dbConnect = new DBConnect(); DemoService demoService = new DemoService(); demoService.setConnect(dbConnect); demoService.doSomeThing(); } }; Thread thread2 = new Thread(){ @Override public void run() { DBConnect dbConnect = new DBConnect(); DemoService demoService = new DemoService(); demoService.setConnect(dbConnect); demoService.doSomeThing(); } }; thread.start(); thread2.start(); // 單例模式改造 // 由DemoService構造器能夠看出,構造這個對象是很是耗時的。而且還不能使用單例模式,由於DBConnect是不能多線程訪問的。遇到這種狀況那就使用ThreadLocal來改造吧。 //若是能修改DemoService源碼,修改源碼便可。若不能修該源碼(好比DemoService是一個三方包)單DemoService不是final的,便可以經過繼承修改。 DemoService demoService1 = new ThreadLocalDemoService(); Thread threadA = new Thread(){ @Override public void run() { demoService1.setConnect(new DBConnect()); demoService1.doSomeThing(); } }; Thread threadB = new Thread(){ @Override public void run() { demoService1.setConnect(new DBConnect()); demoService1.doSomeThing(); } }; threadA.start(); threadB.start(); } static class DemoService{ //這個對象不能線程同時訪問,應該是一個線程就創建一個鏈接到數據庫。不一樣的線程不能使用同一個鏈接。 DBConnect connect; public DemoService(){ try { Thread.sleep(5l); } catch (InterruptedException e) { e.printStackTrace(); } } public void setConnect(DBConnect connect){ this.connect = connect; } public void doSomeThing(){ connect.updateSomeData(); } } //使用ThreadLocal改形成員變量,使其可使其可使用單例模式 static class ThreadLocalDemoService extends DemoService { ThreadLocal<DBConnect> connectThreadLocal = new ThreadLocal<>(); public ThreadLocalDemoService() { super(); } public void doSomeThing(){ connectThreadLocal.get().updateSomeData(); } public void setConnect(DBConnect dbConnect){ connectThreadLocal.set(dbConnect); } } class DBConnect { private String transactionName = Thread.currentThread().getName()+"的事務"; public void updateSomeData(){ System.out.println(transactionName + " update some data"); } }
其中DemoService中有個成語變量DBConnect connect,因爲資源不能同時被連個線程使用,好比socket連接發送數據,或者數據庫事務,一個線程不能影響另一個線程的事務。 這個時候咱們沒有辦法只有對DemoService採用多例模式,單由由於DemoService建立會耗費大量時間。相似的例子不少,好比一個對象中,可能只有少數成員變量不可以多線程訪問,大多數是能多線程訪問的,這時爲了一個成員變量去將單例改爲多例也是很是糟糕的。這時若咱們使用ThreadLocal就可以完美解決。
值得注意的是,ThreadLocal隨着當前線程的銷燬而銷燬,若是程序中採用線程池,在上一次任務運行完以後,記得清掉以前ThreadLocal數據。
實際上學習和使用ThreadLocal以前,也百多過不少ThreadLocal相關的文章。最開始是拜讀了學習Spring必學的Java基礎知識(6)----ThreadLocal@ITEYE這篇文章,才瞭解到ThreadLocal這個東西。最好爲了詳細瞭解,看到了第二篇文章,而且以前看過的關於ThreadLocal的文章與這篇文章內容基本上都同樣,都在講關於Threadloal爲解決線程安全問題提供了新思路,當時被看得一頭霧水。最好看到第三篇的帖子。而後結合源代碼纔對ThreadLocal的本質很好的瞭解。若是你看完本篇文章還不是很是明白,能夠詳細參閱第三篇引用,這個帖子的討論仍是很是精彩的,能給你不少啓迪做用。須要說明的是第二篇CSDN相關講解其實是有問題的。你能夠看看文章下方的評論。