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

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

繼續面試大綱系列文章。java

強烈推薦關注公衆號: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等方法時,會啓動回收這些值。可是若是一直沒調用呢?嗯,很容易就致使內存泄漏了。固然,並不能由於此就認爲是弱引用致使的內存泄露,而應該是,設計的這個變量存儲機制,致使了泄露。因此在使用的時候,要及時釋放(經過以上描述,你確定已經想到怎麼合理釋放了吧?)jvm

 

二.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%的面試挑戰了。若是還有興趣深刻了解,能夠留言交流。

 

歡迎掃描如下二維碼,關注我的公衆號,更及時獲取第一手學習資料:

相關文章
相關標籤/搜索