国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達式|C/C++|IOS|C#|Swift|Android|JavaScript|易語言|

服務(wù)器之家 - 編程語言 - Java教程 - Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析

Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析

2021-05-07 11:15狂小白 Java教程

這篇文章主要給大家介紹了關(guān)于Java并發(fā)編程學(xué)習(xí)之源碼分析ThreadLocal的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

多線程的線程安全問題是微妙而且出乎意料的,因為在沒有進行適當(dāng)同步的情況下多線程中各個操作的順序是不可預(yù)期的,多線程訪問同一個共享變量特別容易出現(xiàn)并發(fā)問題,特別是多個線程需要對一個共享變量進行寫入時候,為了保證線程安全,

一般需要使用者在訪問共享變量的時候進行適當(dāng)?shù)耐剑缦聢D所示:

Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析

可以看到同步的措施一般是加鎖,這就需要使用者對鎖也要有一定了解,這顯然加重了使用者的負擔(dān)。那么有沒有一種方式當(dāng)創(chuàng)建一個變量的時候,每個線程對其進行訪問的時候訪問的是自己線程的變量呢?其實threalocal就可以做這個事情,注意一下,threadlocal的出現(xiàn)并不是為了解決上面的問題而出現(xiàn)的。

threadlocal是在jdk包里面提供的,它提供了線程本地變量,也就是如果你創(chuàng)建了一個threadlocal變量,那么訪問這個變量的每個線程都會有這個變量的本地拷貝,多個線程操作這個變量的時候,實際是操作自己本地內(nèi)存里面的變量,從而避免了線程安全問題,創(chuàng)建一個threadlocal變量后,

每個線程會拷貝一個變量到自己的本地內(nèi)存,如下圖:

Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析

好了,現(xiàn)在我們思考一個問題:threadlocal的實現(xiàn)原理,threadlocal作為變量的線程隔離方式,其內(nèi)部又是如何實現(xiàn)的呢?

首先我們要看threadlocal的類圖結(jié)構(gòu),如下圖所示:

Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析 如

上類圖可見,thread類中有一個threadlocals和inheritablethreadlocals 都是threadlocalmap類型的變量,而threadlocalmap是一個定制化的hashmap,默認每個線程中這兩個變量都為null,只有當(dāng)線程第一次調(diào)用了threadlocal的set或者get方法的時候才會創(chuàng)建。

其實每個線程的本地變量不是存到threadlocal實例里面的,而是存放到調(diào)用線程的threadlocals變量里面。也就是說threadlocal類型的本地變量是存放到具體線程內(nèi)存空間的。 

threadlocal其實就是一個外殼,它通過set方法把value值放入調(diào)用線程threadlocals里面存放起來,當(dāng)調(diào)用線程調(diào)用它的get方法的時候再從當(dāng)前線程的threadlocals變量里面拿出來使用。如果調(diào)用線程如果一直不終止的話,那么這個本地變量會一直存放到調(diào)用線程的threadlocals變量里面,

因此,當(dāng)不需要使用本地變量時候可以通過調(diào)用threadlocal變量的remove方法,從當(dāng)前線程的threadlocals變量里面刪除該本地變量。可能還有人會問threadlocals為什么設(shè)計為map結(jié)構(gòu)呢?很明顯是因為每個線程里面可以關(guān)聯(lián)多個threadlocal變量。

接下來我們可以進入到threadlocal中的源碼如看看,如下代碼所示:

主要看set,get,remove這三個方法的實現(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,去查找對應(yīng)的線程變量,找到則設(shè)置
 threadlocal.threadlocalmap var3 = this.getmap(var2);
 if(var3 != null) {
 var3.set(this, var1);
 } else {       //(3) 第一次調(diào)用則創(chuàng)建當(dāng)前線程對應(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è)置進入到 threadlocals,也就是把當(dāng)前變量值放入了當(dāng)前線程的內(nèi)存變量 threadlocals,threadlocals 是個 hashmap 結(jié)構(gòu),其中 key 就是當(dāng)前 threadlocal 的實例對象引用,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變量。

接下來我們再看get()方法,代碼如下:

?
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,則返回對應(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)前線程實例,如果當(dāng)前線程的threadlocals變量不為null則直接返回當(dāng)前線程的本地變量。否則執(zhí)行代碼(7)進行初始化,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 變量。

接著我們在看看void 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 實例的本地變量。

接下來我們看看具體演示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)啟動線程
 threadone.start();
 threadtwo.start();
 }
}

代碼(2)創(chuàng)建了一個 threadlocal 變量;

代碼(3)、(4)分別創(chuàng)建了線程 1和 2;

代碼(5)啟動了兩個線程;

線程 1 中代碼 3.1 通過 set 方法設(shè)置了 localvariable 的值,這個設(shè)置的其實是線程 1 本地內(nèi)存中的一個拷貝,這個拷貝線程 2 是訪問不了的。然后代碼 3.2 調(diào)用了 print 函數(shù),代碼 1.1 通過 get 函數(shù)獲取了當(dāng)前線程(線程 1)本地內(nèi)存中 localvariable 的值;

線程 2 執(zhí)行類似線程 1。 

運行結(jié)果如下:

Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析

這里要注意一下threadlocal的內(nèi)存泄漏問題

  每個線程內(nèi)部都有一個名字為 threadlocals 的成員變量,該變量類型為 hashmap,其中 key 為我們定義的 threadlocal 變量的 this 引用,value 則為我們 set 時候的值,每個線程的本地變量是存到線程自己的內(nèi)存變量 threadlocals 里面的,如果當(dāng)前線程一直不消失那么這些本地變量會一直存到,

所以可能會造成內(nèi)存泄露,所以使用完畢后要記得調(diào)用 threadlocal 的 remove 方法刪除對應(yīng)線程的 threadlocals 中的本地變量。

解開代碼1.2的注釋后,再次運行,運行結(jié)果如下:

Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析

我們有沒有想過這樣的一個問題:子線程中是否獲取到父線程中設(shè)置的 threadlocal 變量的值呢?

  這里可以告訴大家,在子線程中是獲取不到父線程中設(shè)置的 threadlocal 變量的值的。那么有辦法讓子線程訪問到父線程中的值嗎?為了解決該問題 inheritablethreadlocal 應(yīng)運而生,inheritablethreadlocal 繼承自 threadlocal,提供了一個特性,就是子線程可以訪問到父線程中設(shè)置的本地變量。

首先我們先進入inheritablethreadlocal這個類的源碼去看,如下:

?
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,并重寫了三個方法,在上面的代碼已經(jīng)標出了。代碼(3)可知inheritablethreadlocal重寫createmap方法,那么可以知道現(xiàn)在當(dāng)?shù)谝淮握{(diào)用set方法時候創(chuàng)建的是當(dāng)前線程的inhertablethreadlocals變量的實例,而不再是threadlocals。

代碼(2)可以知道當(dāng)調(diào)用get方法獲取當(dāng)前線程的內(nèi)部map變量時候,獲取的是inheritablethreadlocals,而不再是threadlocals。

關(guān)鍵地方來了,重寫的代碼(1)是何時被執(zhí)行的,以及如何實現(xiàn)子線程可以訪問父線程本地變量的。這個要從thread創(chuàng)建的代碼看起,thread的默認構(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)建線程時候在構(gòu)造函數(shù)里面會調(diào)用init方法,前面講到了inheritablethreadlocal類get,set方法操作的是變量inheritablethreadlocals,所以這里inheritablethreadlocal變量就不為null,所以會執(zhí)行代碼(6),下面看createinheritedmap方法源碼,如下:

?
1
2
3
 static threadlocalmap createinheritedmap(threadlocalmap parentmap) {
return new threadlocalmap(parentmap);
}

可以看到createinheritedmap內(nèi)部使用父線程的inheritablethreadlocals變量作為構(gòu)造函數(shù)創(chuàng)建了一個新的threadlocalmap變量,然后賦值給了子線程的inheritablethreadlocals變量,那么接著進入到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對象,其中代碼(7)inheritablethreadlocal類重寫的代碼(1)也映入眼簾了。

總的來說:inheritablethreadlocal類通過重寫代碼(2)和(3)讓本地變量保存到了具體線程的inheritablethreadlocals變量里面,線程通過inheritablethreadlocal類實例的set 或者 get方法設(shè)置變量時候就會創(chuàng)建當(dāng)前線程的inheritablethreadlocals變量。當(dāng)父線程創(chuàng)建子線程時候,

構(gòu)造函數(shù)里面就會把父線程中inheritablethreadlocals變量里面的本地變量拷貝一份復(fù)制到子線程的inheritablethreadlocals變量里面。

好了原理了解到位了,接下來進行一個例子來驗證上面所了解的東西,如下:

?
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) 啟動子線程
 thread thread = new thread(new runnable() {
  public void run() {
  //(4)子線程輸出線程變量的值
  system.out.println("子線程:" + threadlocal.get());
 
  }
 });
 thread.start();
 //(5)主線程輸出線程變量值
 system.out.println("父線程:" + threadlocal.get());
 }
}

運行結(jié)果如下:

Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析

也就是說同一個 threadlocal 變量在父線程中設(shè)置值后,在子線程中是獲取不到的。根據(jù)上節(jié)的介紹,這個應(yīng)該是正常現(xiàn)象,因為子線程調(diào)用 get 方法時候當(dāng)前線程為子線程,而調(diào)用 set 方法設(shè)置線程變量是 main 線程,兩者是不同的線程,自然子線程訪問時候返回 null。

那么有辦法讓子線程訪問到父線程中的值嗎?答案是有,就用我們上面原理分析的inheritablethreadlocal。

將上面例子的代碼(1)修改為:

?
1
2
//(1) 創(chuàng)建線程變量
public static threadlocal<string> threadlocal = new inheritablethreadlocal<string>();

運行結(jié)果如下:

Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析

可知現(xiàn)在可以從子線程中正常的獲取到線程變量值了。那么什么情況下需要子線程可以獲取到父線程的 threadlocal 變量呢?

  情況還是蠻多的,比如存放用戶登錄信息的 threadlocal 變量,很有可能子線程中也需要使用用戶登錄信息,再比如一些中間件需要用統(tǒng)一的追蹤 id 把整個調(diào)用鏈路記錄下來的情景。

spring request scope 作用域 bean 中 threadlocal 的使用

  我們知道 spring 中在 xml 里面配置 bean 的時候可以指定 scope 屬性來配置該 bean 的作用域為 singleton、prototype、request、session 等,其中作用域為 request 的實現(xiàn)原理就是使用 threadlocal 實現(xiàn)的。如果你想讓你 spring 容器里的某個 bean 擁有 web 的某種作用域,

則除了需要 bean 級上配置相應(yīng)的 scope 屬性,還必須在 web.xml 里面配置如下:

?
1
2
3
<listener>
 <listener-class>org.springframework.web.context.request.requestcontextlistener</listener-class>
</listener>

這里主要看requestcontextlistener的兩個方法:

?
1
public void requestinitialized(servletrequestevent requestevent)

?
1
public void requestdestroyed(servletrequestevent requestevent)

當(dāng)一個web請求過來時候會執(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 {
   //默認inheritable=false
   if (inheritable) {
    inheritablerequestattributesholder.set(attributes);
    requestattributesholder.remove();
   }
   else {
    requestattributesholder.set(attributes);
    inheritablerequestattributesholder.remove();
   }
  }
 }

可以看到上面源碼,由于默認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>,所以具有繼承性,所以默認放入到requestcontextholder里面的屬性值在子線程中獲取不到。

當(dāng)請求結(jié)束時候調(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) {
   // 我們很有可能在最初的請求線程中
   if (attributes == null) {
    attributes = threadattributes;
   }
   //請求結(jié)束則清除當(dāng)前線程的線程變量。
   localecontextholder.resetlocalecontext();
   requestcontextholder.resetrequestattributes();
  }
  if (attributes != null) {
   attributes.requestcompleted();
  }
 }

接下來從時序圖看一下 web請求調(diào)用邏輯如何:

Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析

 也就是說每次發(fā)起一個web請求在tomcat中context(具體應(yīng)用)處理前,host匹配后會設(shè)置下requestcontextholder屬性,讓requestattributesholder不為空,在請求結(jié)束時會清除。

因此,默認情況下放入requestcontextholder里面的屬性子線程訪問不到,spring 的request作用域的bean是使用threadlocal實現(xiàn)的。 

接下來進行一個例子模擬請求,代碼如下:

web.xml配置如下:

因為是 request 作用域,所以必須是 web 項目,并且需要配置 requestcontextlistener 到 web.xml。

?
1
2
3
<listener>
  <listener-class>org.springframework.web.context.request.requestcontextlistener</listener-class>
</listener>

接著注入一個 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>

測試代碼如下:

?
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 的實例到 ioc 容器,最后 testrpc 內(nèi)注入了 requestbean 的實例,方法 test 首先調(diào)用了 requestinfo 的方法 setname 設(shè)置 name 屬性,然后獲取 name 屬性并返回。

這里如果 requestinfo 對象是單例的,那么多個線程同時調(diào)用 test 方法后,每個線程都是設(shè)置-獲取的操作,這個操作不是原子性的,會導(dǎo)致線程安全問題。而這里聲明的作用域為 request 級別,也是每個線程都有一個 requestinfo 的本地變量。 

上面例子方法請求的時序圖如下:

Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析

我們要著重關(guān)注調(diào)用test時候發(fā)生了什么:

Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析

其實前面創(chuàng)建的 requestinfo 是被經(jīng)過 cglib 代理后的(感興趣的可以研究下 scopedproxyfactorybean 這類),所以這里調(diào)用 setname 或者 getname 時候會被 dynamicadvisedinterceptor 攔截的,攔擊器里面最終會調(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ā)起一個請求時候,首先會通過 requestcontextlistener.requestinitialized 里面調(diào)用 requestcontextholder.setrequestattributess 設(shè)置 requestattributesholder。

然后請求被路由到 testrpc 的 test 方法后,test 方法內(nèi)第一次調(diào)用 setname 方法時候,最終會調(diào)用 requestscope.get()方法,get 方法內(nèi)代碼(1)獲取通過 requestcontextlistener.requestinitialized 設(shè)置的線程本地變量 requestattributesholder 保存的屬性集的值。

接著看該屬性集里面是否有名字為 requestinfo 的屬性,由于是第一次調(diào)用,所以不存在,所以會執(zhí)行代碼(2)讓 spring 創(chuàng)建一個 requestinfo 對象,然后設(shè)置到屬性集 attributes,也就是保存到了當(dāng)前請求線程的本地內(nèi)存里面了。然后返回創(chuàng)建的對象,調(diào)用創(chuàng)建對象的 setname。

最后test 方法內(nèi)緊接著調(diào)用了 getname 方法,最終會調(diào)用 requestscope.get() 方法,get 方法內(nèi)代碼(1)獲取通過 requestcontextlistener.requestinitialized 設(shè)置的線程本地變量 requestattributes,然后看該屬性集里面是否有名字為 requestinfo 的屬性,

由于是第一次調(diào)用 setname 時候已經(jīng)設(shè)置名字為 requestinfo 的 bean 到 threadlocal 變量里面了,并且調(diào)用 setname 和 getname 的是同一個線程,所以這里直接返回了調(diào)用 setname 時候創(chuàng)建的 requestinfo 對象,然后調(diào)用它的 getname 方法。

到目前為止我們了解threadlocal 的實現(xiàn)原理,并指出 threadlocal 不支持繼承性;然后緊接著講解了 inheritablethreadlocal 是如何補償了 threadlocal 不支持繼承的特性;最后簡單的介紹了 spring 框架中如何使用 threadlocal 實現(xiàn)了 reqeust scope 的 bean。

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對服務(wù)器之家的支持。

原文鏈接:https://www.cnblogs.com/huangjuncong/p/9127148.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产亚洲精品成人 | av电影免费在线 | 国产午夜精品一区二区三区视频 | a资源在线观看 | 亚洲成av人片一区二区梦乃 | 蜜桃一二三区 | 99视频在线 | 九色影院 | 久久久精品久久久 | 亚洲综合色网 | 九九热视频精品在线观看 | 精品欧美乱码久久久久久1区2区 | 天堂资源在线 | 国内精品视频在线观看 | 九九九久久国产免费 | 九九热在线视频 | 欧美国产在线观看 | 91嫩草国产露脸精品国产 | 天天综合久久 | 精品国产黄a∨片高清在线 黄色大片aaaa | 人人干日日干 | 狠狠的日 | 欧美区国产 | 欧美a在线看 | 黄色免费在线观看 | 久久av一区二区 | 国产精品久久久久久久久久久久久 | 欧美一级在线观看 | 亚洲成人av一区二区 | 一区二区三区欧美 | 欧美日韩免费在线 | 久久久久久久久久久国产 | 免费久久99精品国产婷婷六月 | 欧美午夜一区 | 久久久女女女女999久久 | 中文字幕第5页 | 亚洲不卡在线观看 | 成人一区二区视频 | 日本中文字幕在线观看 | 一区二区免费在线播放 | 久久精品国产一区二区三区不卡 |