繼續面試大綱系列文章。java
(強烈推薦關注公衆號:pnxsxb ,有更多更及時的學習內容分享,還會不按期有專屬於程序員的好禮相送)也能夠長按識別如下二維碼關注:程序員
ThreadLocal和Valotile是兩個比較常見的知識點,雖然簡單,可是能從必定程度上考察一個程序員,對多線程環境下,線程通訊和數據安全的認知。閒話少說,進入正題:面試
看下源碼,以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
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%的面試挑戰了。若是還有興趣深刻了解,能夠留言交流。
歡迎掃描如下二維碼,關注我的公衆號,更及時獲取第一手學習資料: