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