金三銀四跳槽季,BAT美團滴滴java面試大綱(帶答案版)之二:ThreadLocal和Valotile


金三銀四跳槽季,BAT美團滴滴java面試大綱(帶答案版)之二:ThreadLocal和Valotile

繼續面試大綱系列文章。

強烈推薦關注公眾號:pnxsxb   ,有更多更及時的學習內容分享,還會不定期有專屬於程序員的好禮相送)也可以長按識別以下二維碼關注:

  ThreadLocal和Valotile是兩個比較常見的知識點,雖然簡單,但是能從一定程度上考察一個程序員,對多線程環境下,線程通信和數據安全的認知。閑話少說,進入正題:

 

一.ThreadLocal

 

  1. 問:請談談你對ThreadLocal的理解。
  2. 分析:在多線程環境下,我們經常遇到這樣的場景:維護一個全局變量。如果要保證變量值的正確性(或者說變量值修改的原子性),    需用什么方式來實現呢?是的,對修改代碼加鎖可以實現,保證了在同一時刻只有一個線程來修改該變量值。辦法當然不止一                種,並發包AtomicXXX一樣能達到這個效果,原理,差不多,無非是通過鎖來實現並發。那么還有沒有其他思路呢?有,ThreadLocal,實現思路可謂是另辟蹊徑。
  3. 答:每個線程,都會有一個Map(ThreadLocalMap),用來存儲以我們定義的ThreadLocal對象為key,以我們自定義的值為value的  名值對。而這個Map,是來自於我們寫的多線程程序繼承的父線程Thread。以此機制,保證了多線程間該變量值的隔離。

   看下源碼,以get()方法為切入口:

   

復制代碼
 1  public T get() {
 2         Thread t = Thread.currentThread();
 3         ThreadLocalMap map = getMap(t);
 4         if (map != null) {
 5             ThreadLocalMap.Entry e = map.getEntry(this);
 6             if (e != null) {
 7                 @SuppressWarnings("unchecked")
 8                 T result = (T)e.value;
 9                 return result;
10             }
11         }
12         return setInitialValue();
13     }
復制代碼

  重點是第三行,當前線程作為參數傳入,我們來看下getMap(t)做了什么?

1 ThreadLocalMap getMap(Thread t) {
2         return t.threadLocals;
3     }

  是的,拿到當前線程對象的threadLocals對象,我們可以通過方法返回值推斷,是一個ThreadLocalMap類型的對象。那么這個對象在哪定義的呢?繼續看源碼:

1 public class Thread implements Runnable {
2     ......
3     ThreadLocal.ThreadLocalMap threadLocals = null;
4     ......
5 }

  很明顯,是在Thread類里定義。

 

  4.擴展:內存泄漏問題。

       ThreadLocal對象是弱引用。在GC時,會直接回收。這種情況下,Map中的key為null,value值還在,無法得到及時的釋放。目前的策略是在調用get、set、remove等方法時,會啟動回收這些值。但是如果一直沒調用呢?嗯,很容易就導致內存泄漏了。當然,並不能因為此就認為是弱引用導致的內存泄露,而應該是,設計的這個變量存儲機制,導致了泄露。所以在使用的時候,要及時釋放(通過以上描述,你肯定已經想到怎么合理釋放了吧?)

 

二.Valotile

    1.問:請你說下對Valotile的了解,以及使用場景。

 

  2.分析:多線程編程,我們要解決的問題集中在三個方面:

   

        a.原子性,最簡單的例子就是,i++,在多線程環境下,最終的結果是不確定的,為什么?就是因為這么一個++操作,被編譯為指令    后,是多個指令來完成的。那么遇到並發的情況,就會導致彼此“覆蓋”的情況。

   

       b.可見性,通俗解釋就是,在A線程對一個變量做了修改,在B線程中,能正確的讀取到修改后的結果。究其原理,是cpu不是直        接 和系統內存通信,而是把變量讀取到L1,L2等內部的緩存中,也叫作私有的數據工作棧。修改也是在內部緩存中,但是何時              同步到系統內存是不能確定的,有了這個時間差,在並發的時候,就可能會導致,讀到的值,不是最新值。

  

       c.有序性:這里只說指令重排序,虛擬機在把代碼編譯為指令后執行,出於優化的目的,在保證結果不變的情況下,可能會調整指    令的執行順序。

 

   3.答:valotile,能滿足上述的可見性和有序性。但是無法保證原子性。

         可見性,是在修改后,強制把對變量的修改同步到系統內存。而其他cpu在讀取自己的內部緩存中的值的時候,發現是valotile修飾  的,會把內部緩存中的值,置為無效,然后從系統內存讀取。

         有序性,是通過內存屏障來實現的。所謂的內存屏障,可以理解為,在某些指令中,插入屏障指令,用以確保,在向屏障指令后面  繼續執行的時候,其前面的所有指令已經執行完畢。

 

  4.擴展:在寫單例模式時,我們通常會采用雙層判斷的方式,在最內層:

  

instance = new Singleton()

 

  其實這也有一個隱含的問題:這句賦值語句,其實是分三步來操作的:

    a.為instance分配內存

    b.調用Singleto構造函數來初始化變量

    c.instance指向上一步初始化的對象

        在jvm做了指令重排序優化后,上述步驟b和c不能保證,可能出現,c先執行,但是對象卻沒初始化,這時候其他線程判斷的時候,發現是非null,但是使用的時候,卻沒有具體實例,導致報錯。

所以,我們可以用valotile來修飾instance,避免該問題。

有了以上知識儲備,相信可以應對80%的面試挑戰了。如果還有興趣深入了解,可以留言交流。

 

歡迎掃描以下二維碼,關注個人公眾號,更及時獲取第一手學習資料:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM