Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析
前言
多線程的線程安全問(wèn)題是微妙而且出乎意料的,因?yàn)樵跊](méi)有進(jìn)行適當(dāng)同步的情況下多線程中各個(gè)操作的順序是不可預(yù)期的,多線程訪問(wèn)同一個(gè)共享變量特別容易出現(xiàn)并發(fā)問(wèn)題,特別是多個(gè)線程需要對(duì)一個(gè)共享變量進(jìn)行寫(xiě)入時(shí)候,為了保證線程安全,
一般需要使用者在訪問(wèn)共享變量的時(shí)候進(jìn)行適當(dāng)?shù)耐?,如下圖所示:
可以看到同步的措施一般是加鎖,這就需要使用者對(duì)鎖也要有一定了解,這顯然加重了使用者的負(fù)擔(dān)。那么有沒(méi)有一種方式當(dāng)創(chuàng)建一個(gè)變量的時(shí)候,每個(gè)線程對(duì)其進(jìn)行訪問(wèn)的時(shí)候訪問(wèn)的是自己線程的變量呢?其實(shí)ThreaLocal就可以做這個(gè)事情,注意一下,ThreadLocal的出現(xiàn)并不是為了解決上面的問(wèn)題而出現(xiàn)的。
ThreadLocal是在JDK包里面提供的,它提供了線程本地變量,也就是如果你創(chuàng)建了一個(gè)ThreadLocal變量,那么訪問(wèn)這個(gè)變量的每個(gè)線程都會(huì)有這個(gè)變量的本地拷貝,多個(gè)線程操作這個(gè)變量的時(shí)候,實(shí)際是操作自己本地內(nèi)存里面的變量,從而避免了線程安全問(wèn)題,創(chuàng)建一個(gè)ThreadLocal變量后,
每個(gè)線程會(huì)拷貝一個(gè)變量到自己的本地內(nèi)存,如下圖:
好了,現(xiàn)在我們思考一個(gè)問(wèn)題:ThreadLocal的實(shí)現(xiàn)原理,ThreadLocal作為變量的線程隔離方式,其內(nèi)部又是如何實(shí)現(xiàn)的呢?
首先我們要看ThreadLocal的類圖結(jié)構(gòu),如下圖所示:
如
上類圖可見(jiàn),Thread類中有一個(gè)threadLocals和inheritableThreadLocals 都是ThreadLocalMap類型的變量,而ThreadLocalMap是一個(gè)定制化的Hashmap,默認(rèn)每個(gè)線程中這兩個(gè)變量都為null,只有當(dāng)線程第一次調(diào)用了ThreadLocal的set或者get方法的時(shí)候才會(huì)創(chuàng)建。
其實(shí)每個(gè)線程的本地變量不是存到ThreadLocal實(shí)例里面的,而是存放到調(diào)用線程的threadLocals變量里面。也就是說(shuō)ThreadLocal類型的本地變量是存放到具體線程內(nèi)存空間的。
ThreadLocal其實(shí)就是一個(gè)外殼,它通過(guò)set方法把value值放入調(diào)用線程threadLocals里面存放起來(lái),當(dāng)調(diào)用線程調(diào)用它的get方法的時(shí)候再?gòu)漠?dāng)前線程的threadLocals變量里面拿出來(lái)使用。如果調(diào)用線程如果一直不終止的話,那么這個(gè)本地變量會(huì)一直存放到調(diào)用線程的threadLocals變量里面,
因此,當(dāng)不需要使用本地變量時(shí)候可以通過(guò)調(diào)用ThreadLocal變量的remove方法,從當(dāng)前線程的threadLocals變量里面刪除該本地變量??赡苓€有人會(huì)問(wèn)threadLocals為什么設(shè)計(jì)為Map結(jié)構(gòu)呢?很明顯是因?yàn)槊總€(gè)線程里面可以關(guān)聯(lián)多個(gè)ThreadLocal變量。
接下來(lái)我們可以進(jìn)入到ThreadLocal中的源碼如看看,如下代碼所示:
主要看set,get,remove這三個(gè)方法的實(shí)現(xiàn)邏輯,如下:
先看set(T var1)方法
public void set(T var1) { //(1)獲取當(dāng)前線程 Thread var2 = Thread.currentThread(); //(2) 當(dāng)前線程作為key,去查找對(duì)應(yīng)的線程變量,找到則設(shè)置 ThreadLocal.ThreadLocalMap var3 = this.getMap(var2); if(var3 != null) { var3.set(this, var1); } else { //(3) 第一次調(diào)用則創(chuàng)建當(dāng)前線程對(duì)應(yīng)的Hashmap this.createMap(var2, var1); } }
如上代碼(1)首先獲取調(diào)用線程,然后使用當(dāng)前線程作為參數(shù)調(diào)用了 getMap(var2) 方法,getMap(Thread var2) 代碼如下:
ThreadLocal.ThreadLocalMap getMap(Thread var1) { return var1.threadLocals; }
可知getMap(var2) 所作的就是獲取線程自己的變量threadLocals,threadlocal變量是綁定到了線程的成員變量里面。
如果getMap(var2) 返回不為空,則把 value 值設(shè)置進(jìn)入到 threadLocals,也就是把當(dāng)前變量值放入了當(dāng)前線程的內(nèi)存變量 threadLocals,threadLocals 是個(gè) HashMap 結(jié)構(gòu),其中 key 就是當(dāng)前 ThreadLocal 的實(shí)例對(duì)象引用,value 是通過(guò) set 方法傳遞的值。
如果 getMap(var2) 返回空那說(shuō)明是第一次調(diào)用 set 方法,則創(chuàng)建當(dāng)前線程的 threadLocals 變量,下面看 createMap(var2, var1) 里面做了啥呢?
void createMap(Thread var1, T var2) { var1.threadLocals = new ThreadLocal.ThreadLocalMap(this, var2); }
可以看到的就是創(chuàng)建當(dāng)前線程的threadLocals變量。
接下來(lái)我們?cè)倏磄et()方法,代碼如下:
public T get() { //(4)獲取當(dāng)前線程 Thread var1 = Thread.currentThread(); //(5)獲取當(dāng)前線程的threadLocals變量 ThreadLocal.ThreadLocalMap var2 = this.getMap(var1); //(6)如果threadLocals不為null,則返回對(duì)應(yīng)本地變量值 if(var2 != null) { ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this); if(var3 != null) { Object var4 = var3.value; return var4; } } //(7)threadLocals為空則初始化當(dāng)前線程的threadLocals成員變量。 return this.setInitialValue(); }
代碼(4)首先獲取當(dāng)前線程實(shí)例,如果當(dāng)前線程的threadLocals變量不為null則直接返回當(dāng)前線程的本地變量。否則執(zhí)行代碼(7)進(jìn)行初始化,setInitialValue()的代碼如下:
private T setInitialValue() { //(8)初始化為null Object var1 = this.initialValue(); Thread var2 = Thread.currentThread(); ThreadLocal.ThreadLocalMap var3 = this.getMap(var2); //(9)如果當(dāng)前線程變量的threadLocals變量不為空 if(var3 != null) { var3.set(this, var1); //(10)如果當(dāng)前線程的threadLocals變量為空 } else { this.createMap(var2, var1); } return var1; }
如上代碼如果當(dāng)前線程的 threadLocals 變量不為空,則設(shè)置當(dāng)前線程的本地變量值為 null,否者調(diào)用 createMap 創(chuàng)建當(dāng)前線程的 createMap 變量。
接著我們?cè)诳纯磛oid remove()方法,代碼如下:
public void remove() { ThreadLocal.ThreadLocalMap var1 = this.getMap(Thread.currentThread()); if(var1 != null) { var1.remove(this); } }
如上代碼,如果當(dāng)前線程的 threadLocals 變量不為空,則刪除當(dāng)前線程中指定 ThreadLocal 實(shí)例的本地變量。
接下來(lái)我們看看具體演示demo,代碼如下:
/** * Created by cong on 2018/6/3. */ public class ThreadLocalTest { //(1)打印函數(shù) static void print(String str) { //1.1 打印當(dāng)前線程本地內(nèi)存中l(wèi)ocalVariable變量的值 System.out.println(str + ":" + localVariable.get()); //1.2 清除當(dāng)前線程本地內(nèi)存中l(wèi)ocalVariable變量 //localVariable.remove(); } //(2) 創(chuàng)建ThreadLocal變量 static ThreadLocal<String> localVariable = new ThreadLocal<>(); public static void main(String[] args) { //(3) 創(chuàng)建線程one Thread threadOne = new Thread(new Runnable() { public void run() { //3.1 設(shè)置線程one中本地變量localVariable的值 localVariable.set("線程1的本地變量"); //3.2 調(diào)用打印函數(shù) print("線程1----->"); //3.3打印本地變量值 System.out.println("移除線程1本地變量后的結(jié)果" + ":" + localVariable.get()); } }); //(4) 創(chuàng)建線程two Thread threadTwo = new Thread(new Runnable() { public void run() { //4.1 設(shè)置線程one中本地變量localVariable的值 localVariable.set("線程2的本地變量"); //4.2 調(diào)用打印函數(shù) print("線程2----->"); //4.3打印本地變量值 System.out.println("移除線程2本地變量后的結(jié)果" + ":" + localVariable.get()); } }); //(5)啟動(dòng)線程 threadOne.start(); threadTwo.start(); } }
代碼(2)創(chuàng)建了一個(gè) ThreadLocal 變量;
代碼(3)、(4)分別創(chuàng)建了線程 1和 2;
代碼(5)啟動(dòng)了兩個(gè)線程;
線程 1 中代碼 3.1 通過(guò) set 方法設(shè)置了 localVariable 的值,這個(gè)設(shè)置的其實(shí)是線程 1 本地內(nèi)存中的一個(gè)拷貝,這個(gè)拷貝線程 2 是訪問(wèn)不了的。然后代碼 3.2 調(diào)用了 print 函數(shù),代碼 1.1 通過(guò) get 函數(shù)獲取了當(dāng)前線程(線程 1)本地內(nèi)存中 localVariable 的值;
線程 2 執(zhí)行類似線程 1。
運(yùn)行結(jié)果如下:
這里要注意一下ThreadLocal的內(nèi)存泄漏問(wèn)題
每個(gè)線程內(nèi)部都有一個(gè)名字為 threadLocals 的成員變量,該變量類型為 HashMap,其中 key 為我們定義的 ThreadLocal 變量的 this 引用,value 則為我們 set 時(shí)候的值,每個(gè)線程的本地變量是存到線程自己的內(nèi)存變量 threadLocals 里面的,如果當(dāng)前線程一直不消失那么這些本地變量會(huì)一直存到,
所以可能會(huì)造成內(nèi)存泄露,所以使用完畢后要記得調(diào)用 ThreadLocal 的 remove 方法刪除對(duì)應(yīng)線程的 threadLocals 中的本地變量。
解開(kāi)代碼1.2的注釋后,再次運(yùn)行,運(yùn)行結(jié)果如下:
我們有沒(méi)有想過(guò)這樣的一個(gè)問(wèn)題:子線程中是否獲取到父線程中設(shè)置的 ThreadLocal 變量的值呢?
這里可以告訴大家,在子線程中是獲取不到父線程中設(shè)置的 ThreadLocal 變量的值的。那么有辦法讓子線程訪問(wèn)到父線程中的值嗎?為了解決該問(wèn)題 InheritableThreadLocal 應(yīng)運(yùn)而生,InheritableThreadLocal 繼承自 ThreadLocal,提供了一個(gè)特性,就是子線程可以訪問(wèn)到父線程中設(shè)置的本地變量。
首先我們先進(jìn)入InheritableThreadLocal這個(gè)類的源碼去看,如下:
public class InheritableThreadLocal<T> extends ThreadLocal<T> { public InheritableThreadLocal() { } //(1) protected T childValue(T var1) { return var1; } //(2) ThreadLocalMap getMap(Thread var1) { return var1.inheritableThreadLocals; } //(3) void createMap(Thread var1, T var2) { var1.inheritableThreadLocals = new ThreadLocalMap(this, var2); } }
可以看到InheritableThreadlocal繼承ThreadLocal,并重寫(xiě)了三個(gè)方法,在上面的代碼已經(jīng)標(biāo)出了。代碼(3)可知InheritableThreadLocal重寫(xiě)createMap方法,那么可以知道現(xiàn)在當(dāng)?shù)谝淮握{(diào)用set方法時(shí)候創(chuàng)建的是當(dāng)前線程的inhertableThreadLocals變量的實(shí)例,而不再是threadLocals。
代碼(2)可以知道當(dāng)調(diào)用get方法獲取當(dāng)前線程的內(nèi)部map變量時(shí)候,獲取的是inheritableThreadLocals,而不再是threadLocals。
關(guān)鍵地方來(lái)了,重寫(xiě)的代碼(1)是何時(shí)被執(zhí)行的,以及如何實(shí)現(xiàn)子線程可以訪問(wèn)父線程本地變量的。這個(gè)要從Thread創(chuàng)建的代碼看起,Thread的默認(rèn)構(gòu)造函數(shù)以及Thread.java類的構(gòu)造函數(shù)如下:
/** * Created by cong on 2018/6/3. */ public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { //... //(4)獲取當(dāng)前線程 Thread parent = currentThread(); //... //(5)如果父線程的inheritableThreadLocals變量不為null if (parent.inheritableThreadLocals != null) //(6)設(shè)置子線程中的inheritableThreadLocals變量 this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); this.stackSize = stackSize; tid = nextThreadID(); }
創(chuàng)建線程時(shí)候在構(gòu)造函數(shù)里面會(huì)調(diào)用init方法,前面講到了inheritableThreadLocal類get,set方法操作的是變量inheritableThreadLocals,所以這里inheritableThreadLocal變量就不為null,所以會(huì)執(zhí)行代碼(6),下面看createInheritedMap方法源碼,如下:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }
可以看到createInheritedMap內(nèi)部使用父線程的inheritableThreadLocals變量作為構(gòu)造函數(shù)創(chuàng)建了一個(gè)新的ThreadLocalMap變量,然后賦值給了子線程的inheritableThreadLocals變量,那么接著進(jìn)入到ThreadLocalMap的構(gòu)造函數(shù)里面做了什么,源碼如下:
private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { //(7)調(diào)用重寫(xiě)的方法 Object value = key.childValue(e.value);//返回e.value Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }
如上代碼所做的事情就是把父線程的inhertableThreadLocals成員變量的值復(fù)制到新的ThreadLocalMap對(duì)象,其中代碼(7)InheritableThreadLocal類重寫(xiě)的代碼(1)也映入眼簾了。
總的來(lái)說(shuō):InheritableThreadLocal類通過(guò)重寫(xiě)代碼(2)和(3)讓本地變量保存到了具體線程的inheritableThreadLocals變量里面,線程通過(guò)InheritableThreadLocal類實(shí)例的set 或者 get方法設(shè)置變量時(shí)候就會(huì)創(chuàng)建當(dāng)前線程的inheritableThreadLocals變量。當(dāng)父線程創(chuàng)建子線程時(shí)候,
構(gòu)造函數(shù)里面就會(huì)把父線程中inheritableThreadLocals變量里面的本地變量拷貝一份復(fù)制到子線程的inheritableThreadLocals變量里面。
好了原理了解到位了,接下來(lái)進(jìn)行一個(gè)例子來(lái)驗(yàn)證上面所了解的東西,如下:
package com.hjc; /** * Created by cong on 2018/6/3. */ public class InheritableThreadLocalTest { //(1) 創(chuàng)建線程變量 public static ThreadLocal<String> threadLocal = new ThreadLocal<String>(); public static void main(String[] args) { //(2) 設(shè)置線程變量 threadLocal.set("hello Java"); //(3) 啟動(dòng)子線程 Thread thread = new Thread(new Runnable() { public void run() { //(4)子線程輸出線程變量的值 System.out.println("子線程:" + threadLocal.get()); } }); thread.start(); //(5)主線程輸出線程變量值 System.out.println("父線程:" + threadLocal.get()); } }
運(yùn)行結(jié)果如下:
也就是說(shuō)同一個(gè) ThreadLocal 變量在父線程中設(shè)置值后,在子線程中是獲取不到的。根據(jù)上節(jié)的介紹,這個(gè)應(yīng)該是正常現(xiàn)象,因?yàn)樽泳€程調(diào)用 get 方法時(shí)候當(dāng)前線程為子線程,而調(diào)用 set 方法設(shè)置線程變量是 main 線程,兩者是不同的線程,自然子線程訪問(wèn)時(shí)候返回 null。
那么有辦法讓子線程訪問(wèn)到父線程中的值嗎?答案是有,就用我們上面原理分析的InheritableThreadLocal。
將上面例子的代碼(1)修改為:
//(1) 創(chuàng)建線程變量 public static ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();
運(yùn)行結(jié)果如下:
可知現(xiàn)在可以從子線程中正常的獲取到線程變量值了。那么什么情況下需要子線程可以獲取到父線程的 threadlocal 變量呢?
情況還是蠻多的,比如存放用戶登錄信息的 threadlocal 變量,很有可能子線程中也需要使用用戶登錄信息,再比如一些中間件需要用統(tǒng)一的追蹤 ID 把整個(gè)調(diào)用鏈路記錄下來(lái)的情景。
Spring Request Scope 作用域 Bean 中 ThreadLocal 的使用
我們知道 Spring 中在 XML 里面配置 Bean 的時(shí)候可以指定 scope 屬性來(lái)配置該 Bean 的作用域?yàn)?singleton、prototype、request、session 等,其中作用域?yàn)?request 的實(shí)現(xiàn)原理就是使用 ThreadLocal 實(shí)現(xiàn)的。如果你想讓你 Spring 容器里的某個(gè) Bean 擁有 Web 的某種作用域,
則除了需要 Bean 級(jí)上配置相應(yīng)的 scope 屬性,還必須在 web.xml 里面配置如下:
<listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener>
這里主要看RequestContextListener的兩個(gè)方法:
public void requestInitialized(ServletRequestEvent requestEvent)
和
public void requestDestroyed(ServletRequestEvent requestEvent)
當(dāng)一個(gè)web請(qǐng)求過(guò)來(lái)時(shí)候會(huì)執(zhí)行requestInitialized方法:
public void requestInitialized(ServletRequestEvent requestEvent) { .......省略 HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest(); ServletRequestAttributes attributes = new ServletRequestAttributes(request); request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes); LocaleContextHolder.setLocale(request.getLocale()); //設(shè)置屬性到threadlocal變量 RequestContextHolder.setRequestAttributes(attributes); } public static void setRequestAttributes(RequestAttributes attributes) { setRequestAttributes(attributes, false); } public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) { if (attributes == null) { resetRequestAttributes(); } else { //默認(rèn)inheritable=false if (inheritable) { inheritableRequestAttributesHolder.set(attributes); requestAttributesHolder.remove(); } else { requestAttributesHolder.set(attributes); inheritableRequestAttributesHolder.remove(); } } }
可以看到上面源碼,由于默認(rèn)inheritable 為FALSE,我們的屬性值都放到了requestAttributesHoder里面,而它的定義是:
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =new NamedThreadLocal<RequestAttributes>("Request attributes"); private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =new NamedInheritableThreadLocal<RequestAttributes>("Request context");
其中NamedThreadLocal<T> extends ThreadLocal<T>,所以不具有繼承性。
其中 NamedThreadLocal<T> extends ThreadLocal<T>,所以不具有繼承性。
NameInheritableThreadLocal<T> extends InheritableThreadLocal<T>,所以具有繼承性,所以默認(rèn)放入到RequestContextHolder里面的屬性值在子線程中獲取不到。
當(dāng)請(qǐng)求結(jié)束時(shí)候調(diào)用requestDestroyed方法,源碼如下:
public void requestDestroyed(ServletRequestEvent requestEvent) { ServletRequestAttributes attributes = (ServletRequestAttributes) requestEvent.getServletRequest().getAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE); ServletRequestAttributes threadAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (threadAttributes != null) { // 我們很有可能在最初的請(qǐng)求線程中 if (attributes == null) { attributes = threadAttributes; } //請(qǐng)求結(jié)束則清除當(dāng)前線程的線程變量。 LocaleContextHolder.resetLocaleContext(); RequestContextHolder.resetRequestAttributes(); } if (attributes != null) { attributes.requestCompleted(); } }
接下來(lái)從時(shí)序圖看一下 Web請(qǐng)求調(diào)用邏輯如何:
也就是說(shuō)每次發(fā)起一個(gè)Web請(qǐng)求在Tomcat中context(具體應(yīng)用)處理前,host匹配后會(huì)設(shè)置下RequestContextHolder屬性,讓requestAttributesHolder不為空,在請(qǐng)求結(jié)束時(shí)會(huì)清除。
因此,默認(rèn)情況下放入RequestContextHolder里面的屬性子線程訪問(wèn)不到,Spring 的request作用域的bean是使用threadlocal實(shí)現(xiàn)的。
接下來(lái)進(jìn)行一個(gè)例子模擬請(qǐng)求,代碼如下:
web.xml配置如下:
因?yàn)槭?request 作用域,所以必須是 Web 項(xiàng)目,并且需要配置 RequestContextListener 到 web.xml。
<listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener>
接著注入一個(gè) request 作用域 bean 到 IOC 容器。代碼如下:
<bean id="requestBean" class="hjc.test.RequestBean" scope="request"> <property name="name" value="hjc" /> <aop:scoped-proxy /> </bean>
測(cè)試代碼如下:
@WebResource("/testService") public class TestRpc { @Autowired private RequestBean requestInfo; @ResourceMapping("test") public ActionResult test(ErrorContext context) { ActionResult result = new ActionResult(); pvgInfo.setName("hjc"); String name = requestInfo.getName(); result.setValue(name); return result; } }
如上首先配置 RequestContextListener 到 web.xml 里面,然后注入了 Request 作用域的 RequestBean 的實(shí)例到 IOC 容器,最后 TestRpc 內(nèi)注入了 RequestBean 的實(shí)例,方法 test 首先調(diào)用了 requestInfo 的方法 setName 設(shè)置 name 屬性,然后獲取 name 屬性并返回。
這里如果 requestInfo 對(duì)象是單例的,那么多個(gè)線程同時(shí)調(diào)用 test 方法后,每個(gè)線程都是設(shè)置-獲取的操作,這個(gè)操作不是原子性的,會(huì)導(dǎo)致線程安全問(wèn)題。而這里聲明的作用域?yàn)?request 級(jí)別,也是每個(gè)線程都有一個(gè) requestInfo 的本地變量。
上面例子方法請(qǐng)求的時(shí)序圖如下:
我們要著重關(guān)注調(diào)用test時(shí)候發(fā)生了什么:
其實(shí)前面創(chuàng)建的 requestInfo 是被經(jīng)過(guò) CGliB 代理后的(感興趣的可以研究下 ScopedProxyFactoryBean 這類),所以這里調(diào)用 setName 或者 getName 時(shí)候會(huì)被 DynamicAdvisedInterceptor 攔截的,攔擊器里面最終會(huì)調(diào)用到 RequestScope 的 get 方法獲取當(dāng)前線程持有的本地變量。
關(guān)鍵來(lái)了,我們要看一下RequestScope的get方法的源碼如下:
public Object get(String name, ObjectFactory objectFactory) { RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();//(1) Object scopedObject = attributes.getAttribute(name, getScope()); if (scopedObject == null) { scopedObject = objectFactory.getObject();//(2) attributes.setAttribute(name, scopedObject, getScope());//(3) } return scopedObject; }
可知當(dāng)發(fā)起一個(gè)請(qǐng)求時(shí)候,首先會(huì)通過(guò) RequestContextListener.requestInitialized 里面調(diào)用 RequestContextHolder.setRequestAttributess 設(shè)置 requestAttributesHolder。
然后請(qǐng)求被路由到 TestRpc 的 test 方法后,test 方法內(nèi)第一次調(diào)用 setName 方法時(shí)候,最終會(huì)調(diào)用 RequestScope.get()方法,get 方法內(nèi)代碼(1)獲取通過(guò) RequestContextListener.requestInitialized 設(shè)置的線程本地變量 requestAttributesHolder 保存的屬性集的值。
接著看該屬性集里面是否有名字為 requestInfo 的屬性,由于是第一次調(diào)用,所以不存在,所以會(huì)執(zhí)行代碼(2)讓 Spring 創(chuàng)建一個(gè) RequestInfo 對(duì)象,然后設(shè)置到屬性集 attributes,也就是保存到了當(dāng)前請(qǐng)求線程的本地內(nèi)存里面了。然后返回創(chuàng)建的對(duì)象,調(diào)用創(chuàng)建對(duì)象的 setName。
最后test 方法內(nèi)緊接著調(diào)用了 getName 方法,最終會(huì)調(diào)用 RequestScope.get() 方法,get 方法內(nèi)代碼(1)獲取通過(guò) RequestContextListener.requestInitialized 設(shè)置的線程本地變量 RequestAttributes,然后看該屬性集里面是否有名字為 requestInfo 的屬性,
由于是第一次調(diào)用 setName 時(shí)候已經(jīng)設(shè)置名字為 requestInfo 的 bean 到 ThreadLocal 變量里面了,并且調(diào)用 setName 和 getName 的是同一個(gè)線程,所以這里直接返回了調(diào)用 setName 時(shí)候創(chuàng)建的 RequestInfo 對(duì)象,然后調(diào)用它的 getName 方法。
到目前為止我們了解ThreadLocal 的實(shí)現(xiàn)原理,并指出 ThreadLocal 不支持繼承性;然后緊接著講解了 InheritableThreadLocal 是如何補(bǔ)償了 ThreadLocal 不支持繼承的特性;最后簡(jiǎn)單的介紹了 Spring 框架中如何使用 ThreadLocal 實(shí)現(xiàn)了 Reqeust Scope 的 Bean。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
java利用遞歸實(shí)現(xiàn)類別樹(shù)示例代碼
這篇文章主要給大家介紹了關(guān)于java利用遞歸實(shí)現(xiàn)類別樹(shù)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12springmvc無(wú)法訪問(wèn)/WEB-INF/views下的jsp的解決方法
本篇文章主要介紹了springmvc無(wú)法訪問(wèn)/WEB-INF/views下的jsp的解決方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-10-10Java代碼執(zhí)行shell命令的實(shí)現(xiàn)
這篇文章主要介紹了Java代碼執(zhí)行shell命令的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09