自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java面試必問(wèn):ThreadLocal終極篇

開(kāi)發(fā) 后端
張三最近天氣很熱心情不是很好,所以他決定出去面試跟面試官聊聊天排解一下,結(jié)果剛投遞簡(jiǎn)歷就有人約了面試。

[[335272]]

 

本文轉(zhuǎn)載自微信公眾號(hào)「三太子敖丙」,作者三太子敖丙。轉(zhuǎn)載本文請(qǐng)聯(lián)系三太子敖丙公眾號(hào)。

開(kāi)場(chǎng)

張三最近天氣很熱心情不是很好,所以他決定出去面試跟面試官聊聊天排解一下,結(jié)果剛投遞簡(jiǎn)歷就有人約了面試。

我丟,什么情況怎么剛投遞出去就有人約我面試了?誒。。。真煩啊,哥已經(jīng)不在江湖這么久了,江湖還是有哥的傳說(shuō),我還是這么搶手的么?太煩惱了,帥無(wú)罪。

 

暗自竊喜的張三來(lái)到了某東現(xiàn)場(chǎng)面試的辦公室,我丟,這面試官?不是吧,這滿是劃痕的Mac,這發(fā)量,難道就是傳說(shuō)中的架構(gòu)師?

 

張三的心態(tài)一下子就崩了,出來(lái)第一場(chǎng)面試就遇到一個(gè)頂級(jí)面試官,這誰(shuí)頂?shù)米“ ?/p>

你好,我是你的面試官Tony,看我的發(fā)型應(yīng)該你能猜到我的身份了,我也話不說(shuō),我們直接開(kāi)始好不好?看你簡(jiǎn)歷寫(xiě)了多線程,來(lái)你跟我聊一下ThreadLocal吧,我很久沒(méi)寫(xiě)代碼不太熟悉了,你幫我回憶一下。

我丟?這TM是人話?這是什么邏輯啊,說(shuō)是問(wèn)多線程然后一上來(lái)就來(lái)個(gè)這么冷門(mén)的ThreadLocal?心態(tài)崩了呀,再說(shuō)你TM自己忘了不知道下去看看書(shū)么,來(lái)我這里找答案是什么鬼啊...

 

盡管十分不情愿,但是張三還是高速運(yùn)轉(zhuǎn)他的小腦袋,回憶起了ThreadLocal的種種細(xì)節(jié)...

面試官說(shuō)實(shí)話我在實(shí)際開(kāi)發(fā)過(guò)程中用到ThreadLocal的地方不是很多,我在寫(xiě)這個(gè)文章的時(shí)候還刻意去把我電腦上幾十個(gè)項(xiàng)目打開(kāi)之后去全局搜索ThreadLocal發(fā)現(xiàn)除了系統(tǒng)源碼的使用,很少在項(xiàng)目中用到,不過(guò)也還是有的。

 

ThreadLocal的作用主要是做數(shù)據(jù)隔離,填充的數(shù)據(jù)只屬于當(dāng)前線程,變量的數(shù)據(jù)對(duì)別的線程而言是相對(duì)隔離的,在多線程環(huán)境下,如何防止自己的變量被其它線程篡改。

你能跟我說(shuō)說(shuō)它隔離有什么用,會(huì)用在什么場(chǎng)景么?這,我都說(shuō)了我很少用了,還問(wèn)我,難受了呀,哦哦哦,有了想起來(lái)了,事務(wù)隔離級(jí)別。

面試官你好,其實(shí)我第一時(shí)間想到的就是Spring實(shí)現(xiàn)事務(wù)隔離級(jí)別的源碼,這還是當(dāng)時(shí)我大學(xué)被女朋友甩了,一個(gè)人在圖書(shū)館哭泣的時(shí)候無(wú)意間發(fā)現(xiàn)的。

 

Spring采用Threadlocal的方式,來(lái)保證單個(gè)線程中的數(shù)據(jù)庫(kù)操作使用的是同一個(gè)數(shù)據(jù)庫(kù)連接,同時(shí),采用這種方式可以使業(yè)務(wù)層使用事務(wù)時(shí)不需要感知并管理connection對(duì)象,通過(guò)傳播級(jí)別,巧妙地管理多個(gè)事務(wù)配置之間的切換,掛起和恢復(fù)。

Spring框架里面就是用的ThreadLocal來(lái)實(shí)現(xiàn)這種隔離,主要是在TransactionSynchronizationManager這個(gè)類里面,代碼如下所示:

  1. private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class); 
  2.  
  3.  private static final ThreadLocal<Map<Object, Object>> resources = 
  4.    new NamedThreadLocal<>("Transactional resources"); 
  5.  
  6.  private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = 
  7.    new NamedThreadLocal<>("Transaction synchronizations"); 
  8.  
  9.  private static final ThreadLocal<String> currentTransactionName = 
  10.    new NamedThreadLocal<>("Current transaction name"); 
  11.  
  12.   …… 

Spring的事務(wù)主要是ThreadLocal和AOP去做實(shí)現(xiàn)的,我這里提一下,大家知道每個(gè)線程自己的鏈接是靠ThreadLocal保存的就好了,繼續(xù)的細(xì)節(jié)我會(huì)在Spring章節(jié)細(xì)說(shuō)的,暖么?

除了源碼里面使用到ThreadLocal的場(chǎng)景,你自己有使用他的場(chǎng)景么?一般你會(huì)怎么用呢?

來(lái)了來(lái)了,加分項(xiàng)來(lái)了,這個(gè)我還真遇到過(guò),裝B的機(jī)會(huì)終于來(lái)了。

 

有的有的面試官,這個(gè)我會(huì)!!!

之前我們上線后發(fā)現(xiàn)部分用戶的日期居然不對(duì)了,排查下來(lái)是SimpleDataFormat的鍋,當(dāng)時(shí)我們使用SimpleDataFormat的parse()方法,內(nèi)部有一個(gè)Calendar對(duì)象,調(diào)用SimpleDataFormat的parse()方法會(huì)先調(diào)用Calendar.clear(),然后調(diào)用Calendar.add(),如果一個(gè)線程先調(diào)用了add()然后另一個(gè)線程又調(diào)用了clear(),這時(shí)候parse()方法解析的時(shí)間就不對(duì)了。

其實(shí)要解決這個(gè)問(wèn)題很簡(jiǎn)單,讓每個(gè)線程都new 一個(gè)自己的 SimpleDataFormat就好了,但是1000個(gè)線程難道new1000個(gè)SimpleDataFormat?

所以當(dāng)時(shí)我們使用了線程池加上ThreadLocal包裝SimpleDataFormat,再調(diào)用initialValue讓每個(gè)線程有一個(gè)SimpleDataFormat的副本,從而解決了線程安全的問(wèn)題,也提高了性能。

那……還有還有,我還有,您別著急問(wèn)下一個(gè),讓我再加點(diǎn)分,拖延一下面試時(shí)間。

我在項(xiàng)目中存在一個(gè)線程經(jīng)常遇到橫跨若干方法調(diào)用,需要傳遞的對(duì)象,也就是上下文(Context),它是一種狀態(tài),經(jīng)常就是是用戶身份、任務(wù)信息等,就會(huì)存在過(guò)渡傳參的問(wèn)題。

使用到類似責(zé)任鏈模式,給每個(gè)方法增加一個(gè)context參數(shù)非常麻煩,而且有些時(shí)候,如果調(diào)用鏈有無(wú)法修改源碼的第三方庫(kù),對(duì)象參數(shù)就傳不進(jìn)去了,所以我使用到了ThreadLocal去做了一下改造,這樣只需要在調(diào)用前在ThreadLocal中設(shè)置參數(shù),其他地方get一下就好了。

  1. before 
  2.    
  3. void work(User user) { 
  4.     getInfo(user); 
  5.     checkInfo(user); 
  6.     setSomeThing(user); 
  7.     log(user); 
  8.  
  9. then 
  10.    
  11. void work(User user) { 
  12. try{ 
  13.    threadLocalUser.set(user); 
  14.    // 他們內(nèi)部  User u = threadLocalUser.get(); 就好了 
  15.     getInfo(); 
  16.     checkInfo(); 
  17.     setSomeThing(); 
  18.     log(); 
  19.     } finally { 
  20.      threadLocalUser.remove(); 
  21.     } 

我看了一下很多場(chǎng)景的cookie,session等數(shù)據(jù)隔離都是通過(guò)ThreadLocal去做實(shí)現(xiàn)的。

對(duì)了我面試官允許我再秀一下知識(shí)廣度,在Android中,Looper類就是利用了ThreadLocal的特性,保證每個(gè)線程只存在一個(gè)Looper對(duì)象。

  1. static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 
  2. private static void prepare(boolean quitAllowed) { 
  3.     if (sThreadLocal.get() != null) { 
  4.         throw new RuntimeException("Only one Looper may be created per thread"); 
  5.     } 
  6.     sThreadLocal.set(new Looper(quitAllowed)); 

面試官:我丟,這貨怎么知道這么多場(chǎng)景?還把Android都扯了出來(lái),不是吧阿sir,下面我要考考他原理了。

嗯嗯,你回答得很好,那你能跟我說(shuō)說(shuō)他底層實(shí)現(xiàn)的原理么?

好的面試官,我先說(shuō)一下他的使用:

  1. ThreadLocal<String> localName = new ThreadLocal(); 
  2. localName.set("張三"); 
  3. String name = localName.get(); 
  4. localName.remove(); 

其實(shí)使用真的很簡(jiǎn)單,線程進(jìn)來(lái)之后初始化一個(gè)可以泛型的ThreadLocal對(duì)象,之后這個(gè)線程只要在remove之前去get,都能拿到之前set的值,注意這里我說(shuō)的是remove之前。

他是能做到線程間數(shù)據(jù)隔離的,所以別的線程使用get()方法是沒(méi)辦法拿到其他線程的值的,但是有辦法可以做到,我后面會(huì)說(shuō)。

我們先看看他set的源碼:

  1. public void set(T value) { 
  2.     Thread t = Thread.currentThread();// 獲取當(dāng)前線程 
  3.     ThreadLocalMap map = getMap(t);// 獲取ThreadLocalMap對(duì)象 
  4.     if (map != null) // 校驗(yàn)對(duì)象是否為空 
  5.         map.set(this, value); // 不為空set 
  6.     else 
  7.         createMap(t, value); // 為空創(chuàng)建一個(gè)map對(duì)象 

大家可以發(fā)現(xiàn)set的源碼很簡(jiǎn)單,主要就是ThreadLocalMap我們需要關(guān)注一下,而ThreadLocalMap呢是當(dāng)前線程Thread一個(gè)叫threadLocals的變量中獲取的。

  1. ThreadLocalMap getMap(Thread t) { 
  2.         return t.threadLocals; 
  3.     } 
  4. public class Thread implements Runnable { 
  5.       …… 
  6.  
  7.     /* ThreadLocal values pertaining to this thread. This map is maintained 
  8.      * by the ThreadLocal class. */ 
  9.     ThreadLocal.ThreadLocalMap threadLocals = null
  10.  
  11.     /* 
  12.      * InheritableThreadLocal values pertaining to this thread. This map is 
  13.      * maintained by the InheritableThreadLocal class. 
  14.      */ 
  15.     ThreadLocal.ThreadLocalMap inheritableThreadLocals = null
  16.    
  17.      …… 

這里我們基本上可以找到ThreadLocal數(shù)據(jù)隔離的真相了,每個(gè)線程Thread都維護(hù)了自己的threadLocals變量,所以在每個(gè)線程創(chuàng)建ThreadLocal的時(shí)候,實(shí)際上數(shù)據(jù)是存在自己線程Thread的threadLocals變量里面的,別人沒(méi)辦法拿到,從而實(shí)現(xiàn)了隔離。

ThreadLocalMap底層結(jié)構(gòu)是怎么樣子的呢?

面試官這個(gè)問(wèn)題問(wèn)得好啊,內(nèi)心暗罵,讓我歇一會(huì)不行么?

張三笑著回答道,既然有個(gè)Map那他的數(shù)據(jù)結(jié)構(gòu)其實(shí)是很像HashMap的,但是看源碼可以發(fā)現(xiàn),它并未實(shí)現(xiàn)Map接口,而且他的Entry是繼承WeakReference(弱引用)的,也沒(méi)有看到HashMap中的next,所以不存在鏈表了。

  1. static class ThreadLocalMap { 
  2.  
  3.         static class Entry extends WeakReference<ThreadLocal<?>> { 
  4.             /** The value associated with this ThreadLocal. */ 
  5.             Object value; 
  6.  
  7.             Entry(ThreadLocal<?> k, Object v) { 
  8.                 super(k); 
  9.                 value = v; 
  10.             } 
  11.         } 
  12.         …… 
  13.     }     

結(jié)構(gòu)大概長(zhǎng)這樣:

 

稍等,我有兩個(gè)疑問(wèn)你可以解答一下么?

好呀,面試官你說(shuō)。

為什么需要數(shù)組呢?沒(méi)有了鏈表怎么解決Hash沖突呢?

用數(shù)組是因?yàn)?,我們開(kāi)發(fā)過(guò)程中可以一個(gè)線程可以有多個(gè)TreadLocal來(lái)存放不同類型的對(duì)象的,但是他們都將放到你當(dāng)前線程的ThreadLocalMap里,所以肯定要數(shù)組來(lái)存。

至于Hash沖突,我們先看一下源碼:

  1. private void set(ThreadLocal<?> key, Object value) { 
  2.            Entry[] tab = table
  3.             int len = tab.length; 
  4.             int i = key.threadLocalHashCode & (len-1); 
  5.             for (Entry e = tab[i]; 
  6.                  e != null
  7.                  e = tab[i = nextIndex(i, len)]) { 
  8.                 ThreadLocal<?> k = e.get(); 
  9.  
  10.                 if (k == key) { 
  11.                     e.value = value; 
  12.                     return
  13.                 } 
  14.                 if (k == null) { 
  15.                     replaceStaleEntry(key, value, i); 
  16.                     return
  17.                 } 
  18.             } 
  19.             tab[i] = new Entry(key, value); 
  20.             int sz = ++size
  21.             if (!cleanSomeSlots(i, sz) && sz >= threshold) 
  22.                 rehash(); 
  23.         } 

我從源碼里面看到ThreadLocalMap在存儲(chǔ)的時(shí)候會(huì)給每一個(gè)ThreadLocal對(duì)象一個(gè)threadLocalHashCode,在插入過(guò)程中,根據(jù)ThreadLocal對(duì)象的hash值,定位到table中的位置i,int i = key.threadLocalHashCode & (len-1)。

然后會(huì)判斷一下:如果當(dāng)前位置是空的,就初始化一個(gè)Entry對(duì)象放在位置i上;

  1. if (k == null) { 
  2.     replaceStaleEntry(key, value, i); 
  3.     return

如果位置i不為空,如果這個(gè)Entry對(duì)象的key正好是即將設(shè)置的key,那么就刷新Entry中的value;

  1. if (k == key) { 
  2.     e.value = value; 
  3.     return

如果位置i的不為空,而且key不等于entry,那就找下一個(gè)空位置,直到為空為止。

 

這樣的話,在get的時(shí)候,也會(huì)根據(jù)ThreadLocal對(duì)象的hash值,定位到table中的位置,然后判斷該位置Entry對(duì)象中的key是否和get的key一致,如果不一致,就判斷下一個(gè)位置,set和get如果沖突嚴(yán)重的話,效率還是很低的。

以下是get的源碼,是不是就感覺(jué)很好懂了:

  1. private Entry getEntry(ThreadLocal<?> key) { 
  2.            int i = key.threadLocalHashCode & (table.length - 1); 
  3.            Entry e = table[i]; 
  4.            if (e != null && e.get() == key
  5.                return e; 
  6.            else 
  7.                return getEntryAfterMiss(key, i, e); 
  8.        } 
  9.  
  10. private Entry getEntryAfterMiss(ThreadLocal<?> keyint i, Entry e) { 
  11.            Entry[] tab = table
  12.            int len = tab.length; 
  13. / get的時(shí)候一樣是根據(jù)ThreadLocal獲取到table的i值,然后查找數(shù)據(jù)拿到后會(huì)對(duì)比key是否相等  if (e != null && e.get() == key)。 
  14.            while (e != null) { 
  15.                ThreadLocal<?> k = e.get(); 
  16.              // 相等就直接返回,不相等就繼續(xù)查找,找到相等位置。 
  17.                if (k == key
  18.                    return e; 
  19.                if (k == null
  20.                    expungeStaleEntry(i); 
  21.                else 
  22.                    i = nextIndex(i, len); 
  23.                e = tab[i]; 
  24.            } 
  25.            return null
  26.        } 

能跟我說(shuō)一下對(duì)象存放在哪里么?

在Java中,棧內(nèi)存歸屬于單個(gè)線程,每個(gè)線程都會(huì)有一個(gè)棧內(nèi)存,其存儲(chǔ)的變量只能在其所屬線程中可見(jiàn),即棧內(nèi)存可以理解成線程的私有內(nèi)存,而堆內(nèi)存中的對(duì)象對(duì)所有線程可見(jiàn),堆內(nèi)存中的對(duì)象可以被所有線程訪問(wèn)。

那么是不是說(shuō)ThreadLocal的實(shí)例以及其值存放在棧上呢?

其實(shí)不是的,因?yàn)門(mén)hreadLocal實(shí)例實(shí)際上也是被其創(chuàng)建的類持有(更頂端應(yīng)該是被線程持有),而ThreadLocal的值其實(shí)也是被線程實(shí)例持有,它們都是位于堆上,只是通過(guò)一些技巧將可見(jiàn)性修改成了線程可見(jiàn)。

如果我想共享線程的ThreadLocal數(shù)據(jù)怎么辦?

使用InheritableThreadLocal可以實(shí)現(xiàn)多個(gè)線程訪問(wèn)ThreadLocal的值,我們?cè)谥骶€程中創(chuàng)建一個(gè)InheritableThreadLocal的實(shí)例,然后在子線程中得到這個(gè)InheritableThreadLocal實(shí)例設(shè)置的值。

  1. private void test() {     
  2. final ThreadLocal threadLocal = new InheritableThreadLocal();        
  3. threadLocal.set("帥得一匹");     
  4. Thread t = new Thread() {         
  5.     @Override         
  6.     public void run() {             
  7.       super.run();             
  8.       Log.i( "張三帥么 =" + threadLocal.get());         
  9.     }     
  10.   };           
  11.   t.start();  
  12. }  

在子線程中我是能夠正常輸出那一行日志的,這也是我之前面試視頻提到過(guò)的父子線程數(shù)據(jù)傳遞的問(wèn)題。

怎么傳遞的呀?

傳遞的邏輯很簡(jiǎn)單,我在開(kāi)頭Thread代碼提到threadLocals的時(shí)候,你們?cè)偻驴纯次铱桃夥帕肆硗庖粋€(gè)變量:

 

Thread源碼中,我們看看Thread.init初始化創(chuàng)建的時(shí)候做了什么:

  1. public class Thread implements Runnable { 
  2.   …… 
  3.    if (inheritThreadLocals && parent.inheritableThreadLocals != null
  4.       this.inheritableThreadLocals=ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); 
  5.   …… 

我就截取了部分代碼,如果線程的inheritThreadLocals變量不為空,比如我們上面的例子,而且父線程的inheritThreadLocals也存在,那么我就把父線程的inheritThreadLocals給當(dāng)前線程的inheritThreadLocals。

是不是很有意思?

 

小伙子你懂的確實(shí)很多,那你算是一個(gè)深度的ThreadLocal用戶了,你發(fā)現(xiàn)ThreadLocal的問(wèn)題了么?

你是說(shuō)內(nèi)存泄露么?

我丟,這小子為啥知道我要問(wèn)什么?嗯嗯對(duì)的,你說(shuō)一下。

這個(gè)問(wèn)題確實(shí)會(huì)存在的,我跟大家說(shuō)一下為什么,還記得我上面的代碼么?

 

ThreadLocal在保存的時(shí)候會(huì)把自己當(dāng)做Key存在ThreadLocalMap中,正常情況應(yīng)該是key和value都應(yīng)該被外界強(qiáng)引用才對(duì),但是現(xiàn)在key被設(shè)計(jì)成WeakReference弱引用了。

 

我先給大家介紹一下弱引用:

只具有弱引用的對(duì)象擁有更短暫的生命周期,在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過(guò)程中,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存。

不過(guò),由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線程,因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對(duì)象。

這就導(dǎo)致了一個(gè)問(wèn)題,ThreadLocal在沒(méi)有外部強(qiáng)引用時(shí),發(fā)生GC時(shí)會(huì)被回收,如果創(chuàng)建ThreadLocal的線程一直持續(xù)運(yùn)行,那么這個(gè)Entry對(duì)象中的value就有可能一直得不到回收,發(fā)生內(nèi)存泄露。

就比如線程池里面的線程,線程都是復(fù)用的,那么之前的線程實(shí)例處理完之后,出于復(fù)用的目的線程依然存活,所以,ThreadLocal設(shè)定的value值被持有,導(dǎo)致內(nèi)存泄露。

按照道理一個(gè)線程使用完,ThreadLocalMap是應(yīng)該要被清空的,但是現(xiàn)在線程被復(fù)用了。

那怎么解決?

在代碼的最后使用remove就好了,我們只要記得在使用的最后用remove把值清空就好了。

ThreadLocal localName = new ThreadLocal();try { localName.set("張三"); ……} finally { localName.remove();}

remove的源碼很簡(jiǎn)單,找到對(duì)應(yīng)的值全部置空,這樣在垃圾回收器回收的時(shí)候,會(huì)自動(dòng)把他們回收掉。

那為什么ThreadLocalMap的key要設(shè)計(jì)成弱引用?key不設(shè)置成弱引用的話就會(huì)造成和entry中value一樣內(nèi)存泄漏的場(chǎng)景。

補(bǔ)充一點(diǎn):ThreadLocal的不足,我覺(jué)得可以通過(guò)看看netty的fastThreadLocal來(lái)彌補(bǔ),大家有興趣可以康康。

好了,你不僅把我問(wèn)的都回答了,我不知道的你甚至都說(shuō)了,ThreadLocal你過(guò)關(guān)了,不過(guò)JUC的面試才剛剛開(kāi)始,希望你以后越戰(zhàn)越勇,最后拿個(gè)好offer喲。

什么鬼,突然這么煽情,不是很為難我的么?難道是為了鍛煉我?難為大師這樣為我著想,我還一直心里暗罵他,不說(shuō)了回去好好學(xué)了。

總結(jié)

其實(shí)ThreadLocal用法很簡(jiǎn)單,里面的方法就那幾個(gè),算上注釋源碼都沒(méi)多少行,我用了十多分鐘就過(guò)了一遍了,但是在我深挖每一個(gè)方法背后邏輯的時(shí)候,也讓我不得不感慨Josh Bloch 和 Doug Lea的厲害之處。

在細(xì)節(jié)設(shè)計(jì)的處理其實(shí)往往就是我們和大神的區(qū)別,我認(rèn)為很多不合理的點(diǎn),在Google和自己不斷深入了解之后才發(fā)現(xiàn)這才是合理,真的不服不行。

 

ThreadLocal是多線程里面比較冷門(mén)的一個(gè)類,使用頻率比不上別的方法和類,但是通過(guò)我這篇文章,不知道你是否有新的認(rèn)知呢?

 

責(zé)任編輯:武曉燕 來(lái)源: 三太子敖丙
相關(guān)推薦

2023-05-05 08:08:06

JavaRedis事務(wù)

2021-12-09 12:22:28

MyBatis流程面試

2023-06-07 08:08:43

JVM內(nèi)存模型

2020-02-18 14:25:51

Java線程池拒絕策略

2023-08-02 08:54:58

Java弱引用鏈表

2023-02-03 07:24:49

雙親委派模型

2021-01-19 05:24:36

ThreadLocal線程編程

2021-12-27 08:22:18

Kafka消費(fèi)模型

2021-12-06 11:03:57

JVM性能調(diào)優(yōu)

2022-11-04 08:47:52

底層算法數(shù)據(jù)

2020-10-12 18:00:39

JavaAQS代碼

2021-04-26 17:23:21

JavaCAS原理

2022-05-14 21:19:22

ThreadLocaJDKsynchroniz

2020-09-29 15:24:07

面試數(shù)據(jù)結(jié)構(gòu)Hashmap

2023-02-17 08:02:45

@Autowired@Resource

2019-03-15 19:41:39

MySQL面試數(shù)據(jù)庫(kù)

2023-02-01 07:15:16

2020-11-05 13:12:47

紅黑樹(shù)

2024-01-05 14:20:55

MySQL索引優(yōu)化器

2023-07-03 08:17:44

JUC工具代碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)