前言
多線程的線程安全問題是微妙而且出乎意料的,因?yàn)樵跊]有進(jìn)行適當(dāng)同步的情況下多線程中各個(gè)操作的順序是不可預(yù)期的,多線程訪問同一個(gè)共享變量特別容易出現(xiàn)并發(fā)問題,特別是多個(gè)線程需要對(duì)一個(gè)共享變量進(jìn)行寫入時(shí)候,為了保證線程安全,
一般需要使用者在訪問共享變量的時(shí)候進(jìn)行適當(dāng)?shù)耐剑缦聢D所示:
可以看到同步的措施一般是加鎖,這就需要使用者對(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í)候再?gòu)漠?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)方法
1
2
3
4
5
6
7
8
9
|
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) 代碼如下:
1
2
3
|
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) 里面做了啥呢?
1
2
3
|
void createmap(thread var1, t var2) { var1.threadlocals = new threadlocal.threadlocalmap( this , var2); } |
可以看到的就是創(chuàng)建當(dāng)前線程的threadlocals變量。
接下來我們?cè)倏磄et()方法,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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()的代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
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()方法,代碼如下:
1
2
3
4
5
6
|
public void remove() { threadlocal.threadlocalmap var1 = this .getmap(thread.currentthread()); if (var1 != null ) { var1.remove( this ); } } |
如上代碼,如果當(dāng)前線程的 threadlocals 變量不為空,則刪除當(dāng)前線程中指定 threadlocal 實(shí)例的本地變量。
接下來我們看看具體演示demo,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
/** * 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è)類的源碼去看,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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ù)如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/** * 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方法源碼,如下:
1
2
3
|
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ù)里面做了什么,源碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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)證上面所了解的東西,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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)該是正常現(xiàn)象,因?yàn)樽泳€程調(diào)用 get 方法時(shí)候當(dāng)前線程為子線程,而調(diào)用 set 方法設(shè)置線程變量是 main 線程,兩者是不同的線程,自然子線程訪問時(shí)候返回 null。
那么有辦法讓子線程訪問到父線程中的值嗎?答案是有,就用我們上面原理分析的inheritablethreadlocal。
將上面例子的代碼(1)修改為:
1
2
|
//(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 里面配置如下:
1
2
3
|
<listener> <listener- class >org.springframework.web.context.request.requestcontextlistener</listener- class > </listener> |
這里主要看requestcontextlistener的兩個(gè)方法:
1
|
public void requestinitialized(servletrequestevent requestevent) |
和
1
|
public void requestdestroyed(servletrequestevent requestevent) |
當(dāng)一個(gè)web請(qǐng)求過來時(shí)候會(huì)執(zhí)行requestinitialized方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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里面,而它的定義是:
1
2
3
|
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方法,源碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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。
1
2
3
|
<listener> <listener- class >org.springframework.web.context.request.requestcontextlistener</listener- class > </listener> |
接著注入一個(gè) request 作用域 bean 到 ioc 容器。代碼如下:
1
2
3
4
5
|
<bean id= "requestbean" class = "hjc.test.requestbean" scope= "request" > <property name= "name" value= "hjc" /> <aop:scoped-proxy /> </bean> |
測(cè)試代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@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方法的源碼如下:
1
2
3
4
5
6
7
8
9
10
|
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 不支持繼承的特性;最后簡(jiǎn)單的介紹了 spring 框架中如何使用 threadlocal 實(shí)現(xiàn)了 reqeust scope 的 bean。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)服務(wù)器之家的支持。
原文鏈接:https://www.cnblogs.com/huangjuncong/p/9127148.html