摘要:基本原理線程本地變量是和線程相關的變量,一個線程則一份數(shù)據(jù)。其中為聲明的對象。對于一個對象倘若沒有成員變量,單例非常簡單,不用去擔心多線程同時對成員變量修改而產(chǎn)生的線程安全問題。并且還不能使用單例模式,因為是不能多線程訪問的。
ThreadLocal簡述
下面我們看一下ThreadLocal類的官方注釋。
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.,
a user ID or Transaction ID).
大致的意思是,ThreadLocal提供本地線程變量。這個變量里面的值(通過get方法獲?。┦呛推渌€程分割開來的,變量的值只有當前線程能訪問到,不像一般的類型比如Person,Student類型的變量,只要訪問到聲明該變量的對象,即可訪問其全部內容,而且各個線程的訪問的數(shù)據(jù)是無差別的。Thread的典型應用是提供一個與程序運行狀態(tài)相關靜態(tài)變量,比如一次訪問回話的表示符號:USERID,或者一次事務里面的事務id:Transaction ID。
基本原理線程本地變量是和線程相關的變量,一個線程則一份數(shù)據(jù)。我們通過ThreadLocal保存的數(shù)據(jù)最終是保存在Thread類的ThreadLocalMap threadLocals變量中。ThreadlocalMap是一個Map結構,其中key為我們聲明的ThreadLocal對象,value即為我們使用ThreadLocal保存的線程本地變量.
當我們調用ThreadLocal變量set方法時,那么為將TheadLocal作為key,set方法的參數(shù)做為value保存在當前線程的threadLocals中.調用get方法時類似,調用get方法時,會去Thread的threadLocals中去尋找key為ThreadLocal 變量的值
源碼如下:
//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,參數(shù)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 ThreadLocalstringThreadLocal = 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進行參數(shù)傳遞這算是比較正統(tǒng)的ThreadLocal用法,這可能也是ThreadLocal設計的初衷:用來保存于狀態(tài)相關的變量,比如訪問者的用戶信息,事務的事務標識。這里演示一下使用ThreadLocal來傳遞用戶信息,實際上當前流行的大部分權限框架都是使用的ThreadLocal變量來保存用戶信息的。
下面是測試代碼:
//參數(shù)傳遞測試 @Test public void test02(){ //參數(shù)主要利用ThreadLocal是線程的局部變量,只要在同一個線程中,之前設置的值后面就能取到,從而達到參數(shù)值傳遞的效果。 //在前面在線程變量中添加值 stringThreadLocal.set("name"); paramThreadLocal.set(new HashMap(){ { put("id","1"); put("name","xiaoTOT"); put("gender","M"); } }); testParam(); } private void testParam() { //從線程本地變量獲取參數(shù) Map map = paramThreadLocal.get(); map.forEach((key,value)->{ System.out.println("key:"+key+" & value="+value); }); }ThreadLocal改造改造在單例模式中的運用
單例模式的好處無用質疑,可以減少對象的創(chuàng)建。對于那些創(chuàng)建非常費時的對象尤其明顯。并且如果能夠用單例解決的問題經(jīng)歷使用單例解決,這樣能減輕運行時的壓力。
對于一個對象倘若沒有成員變量,單例非常簡單,不用去擔心多線程同時對成員變量修改而產(chǎn)生的線程安全問題。
對于一個擁有成員變量的對象使用單例就需要考慮到線程安全問題。多線程訪問又可以分為下面兩個方面:
a:成員變量需要多線程同步,比如賬戶對象(ACCOUNT)中的成員變量余額(amount).amount成員變量需要在多線程的訪問下保證各個線程保證絕對的同步,即無論什么時候線程內的值都是一樣。我們可以通過加同步關鍵字synchronized,volatile來解決。
b,成員變量不需要線程同步,每個線程訪問自己線程內部的對象。比如一個服務類對數(shù)據(jù)庫的鏈接成員變量,每個線程分配一個連接即可。類似這種場景,我們最簡單的方式是使用多例模式來解決。單更好的方式是通過threadLocal來解決。
下面是使用ThreadLocal改造單例模式的示例:
//ThreadLocal在單例模式改造的運用 @Test public void test03(){ //單例模式的好處無用質疑,可以減少對象的創(chuàng)建。對于那些創(chuàng)建非常費時的對象尤其明顯。并且如果能夠用單例解決的問題經(jīng)歷使用單例解決,這樣能減輕運行時的壓力。 //1,對于一個對象倘若沒有成員變量,單例非常簡單,不用去擔心多線程同時對成員變量修改而產(chǎn)生的線程安全問題。 //2,對于一個擁有成員變量的對象使用單例就需要考慮到線程安全問題。多線程訪問又可以分為下面兩個方面: // a:成員變量需要多線程同步,比如賬戶對象(ACCOUNT)中的成員變量余額(amount).amount成員變量需要在多線程的訪問下保證各個線程保證絕對的同步,即無論什么時候線程內的值都是一樣。 // 我們可以通過加同步關鍵字synchronized,volatile來解決。 // b,成員變量不需要線程同步,每個線程訪問自己線程內部的對象。比如一個服務類對數(shù)據(jù)庫的鏈接成員變量,每個線程分配一個連接即可。類似這種場景,我們最簡單的方式是使用多例模式來解決。 //單更好的方式是通過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{ //這個對象不能線程同時訪問,應該是一個線程就建立一個連接到數(shù)據(jù)庫。不同的線程不能使用同一個連接。 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 { ThreadLocalconnectThreadLocal = 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鏈接發(fā)送數(shù)據(jù),或者數(shù)據(jù)庫事務,一個線程不能影響另外一個線程的事務。 這個時候我們沒有辦法只有對DemoService采用多例模式,單由因為DemoService創(chuàng)建會耗費大量時間。類似的例子很多,比如一個對象中,可能只有少數(shù)成員變量不能夠多線程訪問,大多數(shù)是能多線程訪問的,這時為了一個成員變量去將單例改成多例也是非常糟糕的。這時若我們使用ThreadLocal就能夠完美解決。
備注值得注意的是,ThreadLocal隨著當前線程的銷毀而銷毀,如果程序中采用線程池,在上一次任務運行完之后,記得清掉之前ThreadLocal數(shù)據(jù)。
引用實際上學習和使用ThreadLocal之前,也百多過很多ThreadLocal相關的文章。最開始是拜讀了學習Spring必學的Java基礎知識(6)----ThreadLocal@ITEYE這篇文章,才了解到ThreadLocal這個東西。最好為了詳細了解,看到了第二篇文章,并且之前看過的關于ThreadLocal的文章與這篇文章內容基本上都一樣,都在講關于Threadloal為解決線程安全問題提供了新思路,當時被看得一頭霧水。最好看到第三篇的帖子。然后結合源代碼才對ThreadLocal的本質很好的了解。如果你看完本篇文章還不是非常明白,可以詳細參閱第三篇引用,這個帖子的討論還是非常精彩的,能給你很多啟迪作用。需要說明的是第二篇CSDN相關講解實際上是有問題的。你可以看看文章下方的評論。
學習Spring必學的Java基礎知識(6)----ThreadLocal@ITEYE
徹底理解ThreadLocal@CSDN
正確理解ThreadLocal@ITEYE
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66294.html
摘要:純分享直接上干貨操作系統(tǒng)并發(fā)支持進程管理內存管理文件系統(tǒng)系統(tǒng)進程間通信網(wǎng)絡通信阻塞隊列數(shù)組有界隊列鏈表無界隊列優(yōu)先級有限無界隊列延時無界隊列同步隊列隊列內存模型線程通信機制內存共享消息傳遞內存模型順序一致性指令重排序原則內存語義線程 純分享 , 直接上干貨! 操作系統(tǒng)并發(fā)支持 進程管理內存管...
摘要:找工作之前看了很多面試題,復習資料,但是發(fā)現(xiàn)純看面試題是不行的,因為靠背的東西是記不牢的,需要知識成體系才可以,所以筆者整理了一份復習大綱,基本涵蓋了中高級工程師面試所必須知識點,希望可以通過此文幫助一些想換工作的朋友更好的復習,準備面試。 概述 都說金三銀四青銅五,這幾個月份是程序員最好的跳槽時間,筆者四月初也換了工作。找工作之前看了很多面試題,復習資料,但是發(fā)現(xiàn)純看面試題是不行的,因為靠...
摘要:什么是,簡單翻譯過來就是本地線程,但是直接這么翻譯很難理解的作用,如果換一種說法,可以稱為線程本地存儲。魔數(shù)的選取和斐波那契散列有關,對應的十進制為。而斐波那契散列的乘數(shù)可以用如果把這個值給轉為帶符號的,則會得到。 什么是ThreadLocal ThreadLocal,簡單翻譯過來就是本地線程,但是直接這么翻譯很難理解ThreadLocal的作用,如果換一種說法,可以稱為線程本地存儲。...
摘要:但是還有另外的功能看的后一半代碼作用就是掃描位置之后的數(shù)組直到某一個為的位置,清除每個為的,所以使用可以降低內存泄漏的概率。 在涉及到多線程需要共享變量的時候,一般有兩種方法:其一就是使用互斥鎖,使得在每個時刻只能有一個線程訪問該變量,好處就是便于編碼(直接使用 synchronized 關鍵字進行同步訪問),缺點在于這增加了線程間的競爭,降低了效率;其二就是使用本文要講的 Threa...
閱讀 768·2019-08-29 12:49
閱讀 3557·2019-08-29 11:32
閱讀 3449·2019-08-26 10:43
閱讀 2408·2019-08-23 16:53
閱讀 2055·2019-08-23 15:56
閱讀 1698·2019-08-23 12:03
閱讀 2773·2019-08-23 11:25
閱讀 2090·2019-08-22 15:11