<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          京東一面:說(shuō)出ThreadLocal的使用場(chǎng)景及使用方式

          共 8024字,需瀏覽 17分鐘

           ·

          2021-07-28 02:21

          不點(diǎn)藍(lán)字,我們哪來(lái)故事?

          每天 11 點(diǎn)更新文章,餓了點(diǎn)外賣(mài),點(diǎn)擊 ??《無(wú)門(mén)檻外賣(mài)優(yōu)惠券,每天免費(fèi)領(lǐng)!》

          來(lái)源:jblog.csdn.net/Mind_programmonkey/

          article/details/118220731

          • 兩大使用場(chǎng)景-ThreadLocal的用途
            • 典型場(chǎng)景1:每個(gè)線程需要一個(gè)獨(dú)享的對(duì)象
            • 典型場(chǎng)景2:當(dāng)前用戶信息需要被線程內(nèi)所有方法共享
          • ThreadLocal方法使用總結(jié)
          • ThreadLocal原理
          • ThreadLocal使用問(wèn)題內(nèi)存泄露
          • 實(shí)際應(yīng)用場(chǎng)景-在spring中的實(shí)例分析

          兩大使用場(chǎng)景-ThreadLocal的用途

          典型場(chǎng)景1: 每個(gè)線程需要一個(gè)獨(dú)享的對(duì)象(通常是工具類,典型需要使用的類有SimpleDateFormat和Random)

          典型場(chǎng)景2: 每個(gè)線程內(nèi)需要保存全局變量(例如在攔截器中獲取用戶信息),可以讓不同方法直接使用,避免參數(shù)傳遞的麻煩。

          典型場(chǎng)景1:每個(gè)線程需要一個(gè)獨(dú)享的對(duì)象

          每個(gè)Thread內(nèi)有自己的實(shí)例副本,不共享;

          舉例:SimpleDateFormat。(當(dāng)多個(gè)線程共用這樣一個(gè)SimpleDateFormat,但是這個(gè)類是不安全的)

          • 2個(gè)線程分別用自己的SimpleDateFormat,這沒(méi)問(wèn)題;
          • 后來(lái)延伸出10個(gè),那就有10個(gè)線程和10個(gè)SimpleDateFormat,這雖然寫(xiě)法不優(yōu)雅,但勉強(qiáng)可以接受
          • 但是當(dāng)需求變成了1000,那么必然要用線程池,消耗內(nèi)存太多;
          • 但是每一個(gè)SimpleDateFormat我們都需要?jiǎng)?chuàng)建一遍,那么太耗費(fèi)new對(duì)象了,改成static共用的,所有線程都共用一個(gè)simpleDateFormat對(duì)象,但這是線程不安全的,容易出現(xiàn)時(shí)間一致的情況,在調(diào)用的時(shí)候,可加鎖來(lái)解決,但還是不優(yōu)雅;
          • 用ThreadLocal來(lái)解決該問(wèn)題,給每個(gè)線程分配一個(gè)simpledateformat,可這個(gè)threadlocal是安全的;
          package threadlocal;

          import java.text.SimpleDateFormat;
          import java.util.Date;
          import java.util.concurrent.ExecutorService;
          import java.util.concurrent.Executors;

          /**
           * 描述:     利用ThreadLocal,給每個(gè)線程分配自己的dateFormat對(duì)象,保證了線程安全,高效利用內(nèi)存
           */

          public class ThreadLocalNormalUsage05 {

              public static ExecutorService threadPool = Executors.newFixedThreadPool(10);

              public static void main(String[] args) throws InterruptedException {
                  for (int i = 0; i < 1000; i++) {
                      int finalI = i;
                      threadPool.submit(new Runnable() {
                          @Override
                          public void run() {
                              String date = new ThreadLocalNormalUsage05().date(finalI);
                              System.out.println(date);
                          }
                      });
                  }
                  threadPool.shutdown();
              }

              public String date(int seconds) {
                  //參數(shù)的單位是毫秒,從1970.1.1 00:00:00 GMT計(jì)時(shí)
                  Date date = new Date(1000 * seconds);
          //        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                  SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal2.get();
                  return dateFormat.format(date);
              }
          }

          class ThreadSafeFormatter {

              public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
                  @Override
                  protected SimpleDateFormat initialValue() {
                      return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                  }
              };

              public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal
                      .withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
          }

          典型場(chǎng)景2:當(dāng)前用戶信息需要被線程內(nèi)所有方法共享

          • 一個(gè)比較繁瑣的解決方案是把user作為參數(shù)層層傳遞,從service-1()傳到service-2(),以此類推,但是這樣做會(huì)導(dǎo)致代碼冗余且不易維護(hù)。
          • 進(jìn)階點(diǎn)就是userMap來(lái)保存,但是當(dāng)多線程同時(shí)工作時(shí),需要保證線程安全,需要用synchronized,或者 Concurrenthashmap,但無(wú)論用什么,都會(huì)對(duì)性能有所影響

          每個(gè)線程內(nèi)需要保存全局變量,可以讓不同方法直接使用,避免參數(shù)傳遞的麻煩

          • 用 ThreadLocal 保存一些業(yè)務(wù)內(nèi)存(用戶權(quán)限信息,從用戶系統(tǒng)獲取到的用戶名、userId等)
          • 這些信息在同一個(gè)線程內(nèi)相同,但是不同的線程使用的業(yè)務(wù)內(nèi)容是不相同的
          • 在線程生命周期內(nèi),都通過(guò)這個(gè)靜態(tài)ThreadLocal實(shí)例的get()方法取得自己set過(guò)的那個(gè)對(duì)象,避免了將這個(gè)對(duì)象作為參數(shù)傳遞的麻煩
          package threadlocal;

          /**
           * 描述:     演示ThreadLocal用法2:避免傳遞參數(shù)的麻煩
           */

          public class ThreadLocalNormalUsage06 {

              public static void main(String[] args) {
                  new Service1().process("");

              }
          }

          class Service1 {

              public void process(String name) {
                  User user = new User("超哥");
                  UserContextHolder.holder.set(user);
                  new Service2().process();
              }
          }

          class Service2 {

              public void process() {
                  User user = UserContextHolder.holder.get();
                  ThreadSafeFormatter.dateFormatThreadLocal.get();
                  System.out.println("Service2拿到用戶名:" + user.name);
                  new Service3().process();
              }
          }

          class Service3 {

              public void process() {
                  User user = UserContextHolder.holder.get();
                  System.out.println("Service3拿到用戶名:" + user.name);
                  UserContextHolder.holder.remove();
              }
          }

          class UserContextHolder {

              public static ThreadLocal<User> holder = new ThreadLocal<>();


          }

          class User {

              String name;

              public User(String name) {
                  this.name = name;
              }
          }

          注意點(diǎn):

          • 強(qiáng)調(diào)的是同一個(gè)請(qǐng)求內(nèi)(同一個(gè)線程內(nèi))不同方法見(jiàn)的共享;
          • 不需重寫(xiě)initialValue()方法,但是必須手動(dòng)調(diào)用set()方法

          ThreadLocal方法使用總結(jié)

          場(chǎng)景一:initialValue

          在ThreadLocal第一次get的時(shí)候把對(duì)象給初始化出來(lái),對(duì)象的初始化時(shí)機(jī)可以由我們控制。

          場(chǎng)景二:set

          如果需要保存到ThreadLocal里面的對(duì)象的生成時(shí)機(jī)不由我們隨意控制。例如攔截器生成的用戶信息,用ThreadLocal.set直接放到ThreadLocal當(dāng)中。

          ThreadLocal原理

          理清Thread,ThreadLocalMap以及ThreadLocal

          圖片

          主要方法介紹

          • T initialValue(): 初始化
          • void set(T t): 為這個(gè)線程設(shè)置一新值
          • T get(): 得到這個(gè)線程對(duì)應(yīng)的value。如果是首次調(diào)用get()。則會(huì)調(diào)用initialize來(lái)得到這個(gè)值
          • void remove(): 刪除這個(gè)線程得到的值

          ThreadLocalMap發(fā)生沖突之后,會(huì)用線性探測(cè)法。

          ThreadLocal使用問(wèn)題內(nèi)存泄露

          什么是內(nèi)存泄露

          某個(gè)對(duì)象不再有用,但是占用的內(nèi)存卻不能被回收。

          Value的泄露

          • 在ThreadLocalMap中的每個(gè)Entry都是一個(gè)對(duì)key的弱引用,同時(shí),每個(gè)Entry都包含了一個(gè)對(duì)value的強(qiáng)引用。
          • 正常情況 ,當(dāng)線程終止,保存在ThreadLocal里的value會(huì)被垃圾回收,因?yàn)闆](méi)有任何強(qiáng)引用了。
          • 但是,如果線程不終止(比如線程池需要保持很久),那么key對(duì)應(yīng)的value就不能被回收。Thread->ThreadLocalMap->Entry(key為Null)->Value
          • 因?yàn)関alue和Thread之間還存在這個(gè)強(qiáng)引用鏈路,所以導(dǎo)致value無(wú)法回收,就可能出現(xiàn)OOM;JDK已經(jīng)考慮到這個(gè)問(wèn)題,所以在set,remove,rehash方法中會(huì)掃描key為null,會(huì)把value也設(shè)置為null,這樣value對(duì)象就可以被回收了。
          • 但是如果一個(gè)ThreadLocal不被使用,那么實(shí)際上set,remove,rehash方法也不會(huì)被調(diào)用,如果同時(shí)線程又不停止,那么調(diào)用鏈就一直存在,那么就導(dǎo)致了value的內(nèi)存泄露。
          如何避免內(nèi)存泄露呢?
          • 調(diào)用remove方法,就會(huì)刪除對(duì)應(yīng)的Entry對(duì)象,可以避免內(nèi)存泄露,所以使用完ThreadLocal之后,應(yīng)該調(diào)用remove方法。

          實(shí)際應(yīng)用場(chǎng)景-在spring中的實(shí)例分析

          • DateTimeContextHolder:用到了ThreadLocal
          • RequestContextHolder:用到了ThreadLocal
          - END -

          往期推薦

          消息冪等通用解決方案!

          Redis 生產(chǎn)架構(gòu)選型解決方案

          用好 Java 中的枚舉,讓你的工作效率飛起來(lái)!

          Java工具庫(kù),減少90%代碼量



          下方二維碼關(guān)注我

          技術(shù)草根堅(jiān)持分享 編程,算法,架構(gòu)

          看完文章,餓了點(diǎn)外賣(mài),點(diǎn)擊 ??《無(wú)門(mén)檻外賣(mài)優(yōu)惠券,每天免費(fèi)領(lǐng)!》

          朋友,助攻一把!點(diǎn)個(gè)在看!


          瀏覽 94
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  爱爱一区 | 一级黄色操逼视频 | 国产肏屄青青肏屄视频 | 欧美成人网站在线导航 | 成人激情性爱视频 |