激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

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

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|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)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

多線程的線程安全問題是微妙而且出乎意料的,因?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所示:

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

可以看到同步的措施一般是加鎖,這就需要使用者對(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)存,如下圖:

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

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

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

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

上類圖可見,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é)果如下:

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

這里要注意一下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é)果如下:

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

我們有沒有想過這樣的一個(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é)果如下:

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

也就是說同一個(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é)果如下:

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

可知現(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)用邏輯如何:

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

 也就是說每次發(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í)序圖如下:

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

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

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

其實(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

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 奶子吧naiziba.cc免费午夜片在线观看 | 91成人在线免费观看 | 免费在线观看成人av | 草草久久久 | 在线观看日本中文字幕 | 色综合久久久久久 | 黄色一级片在线观看 | 国产1区视频 | 在线成人av观看 | 激情综合在线 | 久草亚洲视频 | 欧美成人一区免费视频 | 国产超碰人人爽人人做人人爱 | 免费国产在线观看 | 91一区二区在线观看 | 久久国产综合视频 | 国产美女视频一区二区三区 | 久色porn| 九九热视频免费在线观看 | 免费激情网站 | chinese-xvideos | 欧美一区二区三区不卡免费观看 | 国产午夜电影在线观看 | 中文字幕亚洲一区二区三区 | 中国一级毛片在线视频 | 黄色片在线免费播放 | 毛片一级片 | qyl在线视频精品免费观看 | 欧美一级电影网 | 国产精品成aⅴ人片在线观看 | 欧美视频一二三区 | 国产精选电影免费在线观看 | 日韩视频中文 | 宅男噜噜噜66国产在线观看 | 国产免费福利视频 | 欧美视频在线观看一区 | 久久久tv| 成人毛片免费在线 | 欧美无极品 | 91女上位 在线播放 bt 自拍 另类 综合 欧美 | 中文字幕线观看 |