欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2018年06月04日 10:06:34   作者:狂小白  
這篇文章主要給大家介紹了關(guān)于Java并發(fā)編程學(xué)習(xí)之源碼分析ThreadLocal的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

多線程的線程安全問(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 中的單元測(cè)試

    淺談Java 中的單元測(cè)試

    這篇文章主要介紹了Java 中的單元測(cè)試的相關(guān)資料,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2020-09-09
  • java利用遞歸實(shí)現(xiàn)類別樹(shù)示例代碼

    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-12
  • SpringCloud gateway跨域配置的操作

    SpringCloud gateway跨域配置的操作

    這篇文章主要介紹了SpringCloud gateway跨域配置的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • springmvc無(wú)法訪問(wèn)/WEB-INF/views下的jsp的解決方法

    springmvc無(wú)法訪問(wèn)/WEB-INF/views下的jsp的解決方法

    本篇文章主要介紹了springmvc無(wú)法訪問(wèn)/WEB-INF/views下的jsp的解決方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2017-10-10
  • 詳解shrio的認(rèn)證(登錄)過(guò)程

    詳解shrio的認(rèn)證(登錄)過(guò)程

    這篇文章主要介紹了shrio的認(rèn)證(登錄)過(guò)程,幫助大家更好的理解和使用shrio框架,感興趣的朋友可以了解下
    2021-02-02
  • 詳解Java設(shè)計(jì)模式之備忘錄模式的使用

    詳解Java設(shè)計(jì)模式之備忘錄模式的使用

    這篇文章主要介紹了Java設(shè)計(jì)模式之備忘錄模式的使用,備忘錄模式中的發(fā)起者和管需要的朋友可以參考下
    2016-02-02
  • Java代碼執(zhí)行shell命令的實(shí)現(xiàn)

    Java代碼執(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
  • 淺談JVM垃圾回收有哪些常用算法

    淺談JVM垃圾回收有哪些常用算法

    今天給大家?guī)?lái)的是關(guān)于Java虛擬機(jī)的相關(guān)知識(shí),文章圍繞著JVM垃圾回收有哪些常用算法展開(kāi),文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • URL @PathVariable 變量的匹配原理分析

    URL @PathVariable 變量的匹配原理分析

    這篇文章主要介紹了URL @PathVariable 變量的匹配原理分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • Java原子類中的AtomicInteger類詳解

    Java原子類中的AtomicInteger類詳解

    這篇文章主要介紹了Java原子類中的AtomicInteger類詳解,原子類可以保證對(duì)"變量"操作的,原子性、有序性、可見(jiàn)性,我們可以通過(guò)AtomicInteger類,來(lái)看看它們是怎樣工作的,需要的朋友可以參考下
    2023-10-10

最新評(píng)論