Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析
前言
多線程的線程安全問題是微妙而且出乎意料的,因?yàn)樵跊]有進(jìn)行適當(dāng)同步的情況下多線程中各個(gè)操作的順序是不可預(yù)期的,多線程訪問同一個(gè)共享變量特別容易出現(xiàn)并發(fā)問題,特別是多個(gè)線程需要對(duì)一個(gè)共享變量進(jìn)行寫入時(shí)候,為了保證線程安全,
一般需要使用者在訪問共享變量的時(shí)候進(jìn)行適當(dāng)?shù)耐?,如下圖所示:
可以看到同步的措施一般是加鎖,這就需要使用者對(duì)鎖也要有一定了解,這顯然加重了使用者的負(fù)擔(dān)。那么有沒有一種方式當(dāng)創(chuàng)建一個(gè)變量的時(shí)候,每個(gè)線程對(duì)其進(jìn)行訪問的時(shí)候訪問的是自己線程的變量呢?其實(shí)ThreaLocal就可以做這個(gè)事情,注意一下,ThreadLocal的出現(xiàn)并不是為了解決上面的問題而出現(xiàn)的。
ThreadLocal是在JDK包里面提供的,它提供了線程本地變量,也就是如果你創(chuàng)建了一個(gè)ThreadLocal變量,那么訪問這個(gè)變量的每個(gè)線程都會(huì)有這個(gè)變量的本地拷貝,多個(gè)線程操作這個(gè)變量的時(shí)候,實(shí)際是操作自己本地內(nèi)存里面的變量,從而避免了線程安全問題,創(chuàng)建一個(gè)ThreadLocal變量后,
每個(gè)線程會(huì)拷貝一個(gè)變量到自己的本地內(nèi)存,如下圖:

好了,現(xiàn)在我們思考一個(gè)問題:ThreadLocal的實(shí)現(xiàn)原理,ThreadLocal作為變量的線程隔離方式,其內(nèi)部又是如何實(shí)現(xiàn)的呢?
首先我們要看ThreadLocal的類圖結(jié)構(gòu),如下圖所示:
如
上類圖可見,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變量里面。也就是說ThreadLocal類型的本地變量是存放到具體線程內(nèi)存空間的。
ThreadLocal其實(shí)就是一個(gè)外殼,它通過set方法把value值放入調(diào)用線程threadLocals里面存放起來,當(dāng)調(diào)用線程調(diào)用它的get方法的時(shí)候再從當(dāng)前線程的threadLocals變量里面拿出來使用。如果調(diào)用線程如果一直不終止的話,那么這個(gè)本地變量會(huì)一直存放到調(diào)用線程的threadLocals變量里面,
因此,當(dāng)不需要使用本地變量時(shí)候可以通過調(diào)用ThreadLocal變量的remove方法,從當(dāng)前線程的threadLocals變量里面刪除該本地變量??赡苓€有人會(huì)問threadLocals為什么設(shè)計(jì)為Map結(jié)構(gòu)呢?很明顯是因?yàn)槊總€(gè)線程里面可以關(guān)聯(lián)多個(gè)ThreadLocal變量。
接下來我們可以進(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 是通過 set 方法傳遞的值。
如果 getMap(var2) 返回空那說明是第一次調(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變量。
接下來我們?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í)例的本地變量。
接下來我們看看具體演示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 通過 set 方法設(shè)置了 localVariable 的值,這個(gè)設(shè)置的其實(shí)是線程 1 本地內(nèi)存中的一個(gè)拷貝,這個(gè)拷貝線程 2 是訪問不了的。然后代碼 3.2 調(diào)用了 print 函數(shù),代碼 1.1 通過 get 函數(shù)獲取了當(dāng)前線程(線程 1)本地內(nèi)存中 localVariable 的值;
線程 2 執(zhí)行類似線程 1。
運(yùn)行結(jié)果如下:
這里要注意一下ThreadLocal的內(nèi)存泄漏問題
每個(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 中的本地變量。
解開代碼1.2的注釋后,再次運(yùn)行,運(yùn)行結(jié)果如下:
我們有沒有想過這樣的一個(gè)問題:子線程中是否獲取到父線程中設(shè)置的 ThreadLocal 變量的值呢?
這里可以告訴大家,在子線程中是獲取不到父線程中設(shè)置的 ThreadLocal 變量的值的。那么有辦法讓子線程訪問到父線程中的值嗎?為了解決該問題 InheritableThreadLocal 應(yīng)運(yùn)而生,InheritableThreadLocal 繼承自 ThreadLocal,提供了一個(gè)特性,就是子線程可以訪問到父線程中設(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,并重寫了三個(gè)方法,在上面的代碼已經(jīng)標(biāo)出了。代碼(3)可知InheritableThreadLocal重寫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)鍵地方來了,重寫的代碼(1)是何時(shí)被執(zhí)行的,以及如何實(shí)現(xià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)用重寫的方法
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類重寫的代碼(1)也映入眼簾了。
總的來說:InheritableThreadLocal類通過重寫代碼(2)和(3)讓本地變量保存到了具體線程的inheritableThreadLocals變量里面,線程通過InheritableThreadLocal類實(shí)例的set 或者 get方法設(shè)置變量時(shí)候就會(huì)創(chuàng)建當(dāng)前線程的inheritableThreadLocals變量。當(dāng)父線程創(chuàng)建子線程時(shí)候,
構(gòu)造函數(shù)里面就會(huì)把父線程中inheritableThreadLocals變量里面的本地變量拷貝一份復(fù)制到子線程的inheritableThreadLocals變量里面。
好了原理了解到位了,接下來進(jìn)行一個(gè)例子來驗(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é)果如下:
也就是說同一個(gè) ThreadLocal 變量在父線程中設(shè)置值后,在子線程中是獲取不到的。根據(jù)上節(jié)的介紹,這個(gè)應(yīng)該是正?,F(xiàn)象,因?yàn)樽泳€程調(diào)用 get 方法時(shí)候當(dāng)前線程為子線程,而調(diào)用 set 方法設(shè)置線程變量是 main 線程,兩者是不同的線程,自然子線程訪問時(shí)候返回 null。
那么有辦法讓子線程訪問到父線程中的值嗎?答案是有,就用我們上面原理分析的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)用鏈路記錄下來的情景。
Spring Request Scope 作用域 Bean 中 ThreadLocal 的使用
我們知道 Spring 中在 XML 里面配置 Bean 的時(shí)候可以指定 scope 屬性來配置該 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)求過來時(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();
}
}
接下來從時(shí)序圖看一下 Web請(qǐng)求調(diào)用邏輯如何:
也就是說每次發(fā)起一個(gè)Web請(qǐng)求在Tomcat中context(具體應(yīng)用)處理前,host匹配后會(huì)設(shè)置下RequestContextHolder屬性,讓requestAttributesHolder不為空,在請(qǐng)求結(jié)束時(shí)會(huì)清除。
因此,默認(rèn)情況下放入RequestContextHolder里面的屬性子線程訪問不到,Spring 的request作用域的bean是使用threadlocal實(shí)現(xiàn)的。
接下來進(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)致線程安全問題。而這里聲明的作用域?yàn)?request 級(jí)別,也是每個(gè)線程都有一個(gè) requestInfo 的本地變量。
上面例子方法請(qǐng)求的時(shí)序圖如下:
我們要著重關(guān)注調(diào)用test時(shí)候發(fā)生了什么:
其實(shí)前面創(chuàng)建的 requestInfo 是被經(jīng)過 CGliB 代理后的(感興趣的可以研究下 ScopedProxyFactoryBean 這類),所以這里調(diào)用 setName 或者 getName 時(shí)候會(huì)被 DynamicAdvisedInterceptor 攔截的,攔擊器里面最終會(huì)調(diào)用到 RequestScope 的 get 方法獲取當(dāng)前線程持有的本地變量。
關(guān)鍵來了,我們要看一下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ì)通過 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)獲取通過 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)獲取通過 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 不支持繼承的特性;最后簡單的介紹了 Spring 框架中如何使用 ThreadLocal 實(shí)現(xiàn)了 Reqeust Scope 的 Bean。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Java報(bào)錯(cuò):ClassCastException問題解決方法
異常是程序中的一些錯(cuò)誤,但并不是所有的錯(cuò)誤都是異常,并且錯(cuò)誤有時(shí)候是可以避免的,下面這篇文章主要給大家介紹了關(guān)于Java報(bào)錯(cuò):ClassCastException問題解決方法,需要的朋友可以參考下2024-07-07
淺談關(guān)于Java正則和轉(zhuǎn)義中\(zhòng)\和\\\\的理解
這篇文章主要介紹了淺談關(guān)于Java正則和轉(zhuǎn)義中\(zhòng)\和\\\\的理解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
深入解析Java的Hibernate框架中的一對(duì)一關(guān)聯(lián)映射
這篇文章主要介紹了Java的Hibernate框架的一對(duì)一關(guān)聯(lián)映射,包括對(duì)一對(duì)一外聯(lián)映射的講解,需要的朋友可以參考下2016-01-01
解決java.lang.NoClassDefFoundError錯(cuò)誤的問題
在Java開發(fā)過程中,NoClassDefFoundError是一個(gè)常見的運(yùn)行時(shí)錯(cuò)誤,是由于JVM在運(yùn)行時(shí)找不到已編譯的類文件導(dǎo)致的,本文就來介紹一下如何解決,具有一定的參考價(jià)值,感興趣的可以了解一下2024-09-09
解決Aop @AfterReturning因返回類型不一致導(dǎo)致無法執(zhí)行切面代碼
這篇文章主要介紹了解決Aop @AfterReturning因返回類型不一致導(dǎo)致無法執(zhí)行切面代碼問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
Java StringBuilder類相關(guān)知識(shí)總結(jié)
這篇文章主要介紹了Java StringBuilder類相關(guān)知識(shí)總結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
Spring-data-redis操作redis知識(shí)總結(jié)
這篇文章主要介紹了Spring-data-redis操作redis知識(shí)總結(jié),spring-data-redis是spring-data模塊的一部分,專門用來支持在spring管理項(xiàng)目對(duì)redis的操作。2017-04-04

