<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ## 44 場景實戰:ThreadLocal 在上下文傳值場景下的實踐 ## 開篇語 我們在 《打動面試官:線程池流程編排中的運用實戰》一文中將流程引擎簡單地完善了一下,本文在其基礎上繼續進行改造,建議同學可以先看看 GitHub 上的代碼,或者看看之前的文章。 ### 1 回顧 流程引擎編排的對象,我們稱為組件(就是 SpringBean),之前我們給組件定義了通用的接口,組件實現時就實現這個接口,代碼如下: ![](https://img.kancloud.cn/f0/1c/f01c10b48f55c441e595e23cabd62758_1639x1246.jpg) 我們定義了 DomainAbilityBean 接口,入參和出參都是 FlowContent,FlowContent 我們稱為上下文。 ### 2 ThreadLocal 實現 上下文傳參除了 FlowContent 實現外,ThreadLocal 也是可以實現的,我們來演示一下: #### 2.1 定義 ThreadLocal 上下文工具類 首先我們使用 ThreadLocal 定義了上下文工具類,并且定義了 put、get 方法,方便使用,代碼如下: ``` public class ContextCache implements Serializable { private static final long serialVersionUID = 2136539028591849277L; // 使用 ThreadLocal 緩存上下文信息 public static final ThreadLocal<Map<String,String>> CACHE = new ThreadLocal<>(); /** * 放數據 * @param sourceKey */ public static final void putAttribute(String sourceKey,String value){ Map<String,String> cacheMap = CACHE.get(); if(null == cacheMap){ cacheMap = new HashMap<>(); } cacheMap.put(sourceKey,value); CACHE.set(cacheMap); } /** * 拿數據 * @param sourceKey */ public static final String getAttribute(String sourceKey){ Map<String,String> cacheMap = CACHE.get(); if(null == cacheMap){ return null; } return cacheMap.get(sourceKey); } } ``` 如果你想往 ThreadLocal 放數據,調用 ContextCache.putAttribute 方法,如果想從 ThreadLocal 拿數據,調用 ContextCache.getAttribute 方法即可。 我們寫了兩個組件,一個組件放數據,一個組件拿數據,如下: ![](https://img.kancloud.cn/67/b1/67b17e3d22be24346c9eabf2d536966f_1447x1256.jpg) 我們把兩個 SpringBean 注冊到流程注冊中心中,讓其按照先執行 BeanThree 再執行 BeanFive 的順序進行執行,運行 DemoApplication 類的 main 方法進行執行,執行結果如下: ![](https://img.kancloud.cn/d6/57/d6579a937814a44f72584cc2b54ed1da_1778x228.jpg) 從打印的日志可以看到,在 Spring 容器管理的 SpringBean 中,ThreadLocal 也是可以儲存中間緩存值的。 ### 3 開啟子線程 我們做一個實驗,我們在 BeanFive 中開啟子線程,然后再從 ThreadLocal 中拿值,看看能否拿到值,BeanFive 的代碼修改成如下: ![](https://img.kancloud.cn/44/b5/44b5cef6dbc8c35102d6366ba569136b_1704x665.jpg) 我們再來運行一下,打印的日志如下: ![](https://img.kancloud.cn/8b/c0/8bc0a61d0cc847b0112ad8ebcf8db3a5_2001x500.jpg) 從打印的日志中,我們發現在子線程中從 ThreadLocal 取值時,并沒有取得值,這個原因主要是我們之前說的,線程在創建的時候,并不會把父線程的 ThreadLocal 中的值拷貝給子線程的 ThreadLocal,解決方案就是把 ThreadLocal 修改成 InheritableThreadLocal,代碼修改如下: ![](https://img.kancloud.cn/7e/5a/7e5a3690442dd87fb2ac9b2ab09ea910_2036x392.jpg) 我們再次運行,結果如下: ![](https://img.kancloud.cn/44/07/4407e9f841c0dcdb36a8b00c329080a1_1498x216.jpg) 從運行結果看,我們成功的在子線程中拿到值。 ### 4 線程池 + ThreadLocal 如果是拿數據的 springBean 是丟給線程池執行的,我們能夠成功的從 ThreadLocal 中拿到數據么? 首先我們在放數據的 springBean 中,把放的值修改成隨機的,接著拿數據的 SpringBean 修改成異步執行,代碼修改如下: ![](https://img.kancloud.cn/bc/87/bc878391f933325ffa598c1dfe2be1aa_1920x1303.jpg) 為了能快速看到效果,我們把線程池的 coreSize 和 maxSize 全部修改成 3,并讓任務沉睡一段時間,這樣三個線程肯定消費不完任務,大量任務都會到隊列中去排隊,我們修改一下測試腳本,如下: ![](https://img.kancloud.cn/e7/82/e782c1d8552ac15d042724243bae8bc1_1394x340.jpg) 我們期望的結果: 1. 線程池中執行的 BeanFive 可以成功從 ThreadLocal 中拿到數據; 2. 能夠從 ThreadLocal 拿到正確的數據,比如 BeanThree 剛放進 key1,value5,那么期望在 BeanFive 中根據 key1 能拿出 value5,而不是其它值。 我們運行一下,結果如下: ![](https://img.kancloud.cn/c0/b3/c0b3cf5d50a333ed870d577442edd12d_2024x874.jpg) 從結果中可以看到,并沒有符合我們的預期,我們往 ThreadLocal 中 put 進很多值,但最后拿出來的值卻很多都是 value379,都為最后 put 到 ThreadLocal 中的值。 這個原因主要是 ThreadLocal 存儲的 HashMap 的引用都是同一個,main 主線程可以修改 HashMap 中的值,子線程從 ThreadLocal 中拿值時,也是從 HashMap 中拿值,從而導致不能把 put 的值通過 ThreadLocal 正確的傳遞給子線程。 為了證明是這個原因,我們在從 ThreadLocal 放、拿值的地方,把 HashMap 的內存地址都打印出來,改動代碼如下: ![](https://img.kancloud.cn/b0/d1/b0d1a3dc8f1616645fce95b84b80288d_1809x1349.jpg) 我們再次運行測試代碼,運行的結果如下: ![](https://img.kancloud.cn/86/22/8622db2dd1dbc42855a7f370a4b6f5e0_1015x820.jpg) 從測試結果中可以看到,不管是主線程還是子線程和 ThreadLocal 進行交互時,HashMap 都是同一個,也就是說 ThreadLocal 中保存的 HashMap 是共享的,這就導致了線程安全的問題,子線程讀取到的值就會混亂掉。 ### 5 解決方案 針對這個問題,我們提出了一種解決方案,在把任務提交到線程池時,我們進行 HashMap 的拷貝,這樣子線程的 HashMap 和 main 線程的 HashMap 就不同了,可以解決上面的問題。 我們提交任務時, 使用的是 Runnable,要實現 HashMap 的拷貝的話,我們需要把 Runnable 進行一層包裝,包裝的代碼如下: ![](https://img.kancloud.cn/16/94/1694b6ad1e35aab18bcf911311320480_800x913.jpg) 運行結果如下: ![](https://img.kancloud.cn/0e/70/0e70402815ac7488bd7b42d9e2f00720_934x820.jpg) 從運行結果中可以看出,線程池拿出來的 value 已經是正確的了。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看