1)作用
ThreadLocal 不是用於解決共享變數的問題的,也不是為了協調執行緒同步而存在,而是為了方便每個執行緒處理自己的狀態而引入的一個機制;每個Thread內部都有一個ThreadLocal.ThreadLocalMap型別的成員變數,該成員變數用來儲存實際的ThreadLocal變數副本;ThreadLocal並不是為執行緒儲存物件的副本,它僅僅只起到一個索引的作用;它的主要目的是為每一個執行緒隔離一個類的例項,這個例項的作用範圍僅限於執行緒內部;2)原理
(1)ThreadLocal的資料結構
Thread、ThreadLocal、ThreadLocalMap的關係:ThreadLocal:屬於Thread的成員變數;ThreadLocalMap:屬於ThreadLocal的內部靜態類;(2)ThreadLocal的原始碼
ThreadLocalMap1> Entrystatic class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; }}
ThreadLocalMap其內部利用Entry來實現key-value的儲存,Entry所對應key(ThreadLocal例項)的引用為一個弱引用;
2>set(ThreadLocal key, Object value)
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; // 根據 ThreadLocal 的雜湊值,查詢對應元素在陣列中的位置 int i = key.threadLocalHashCode & (len-1); // 採用“線性探測法”,尋找合適位置 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); // key 存在,直接覆蓋 if (k == key) { e.value = value; return; } // key == null,但是存在值(因為此處的e != null),說明之前的ThreadLocal物件已經被回收了 if (k == null) { // 用新元素替換陳舊的元素 replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash();}
這個set()操作y與集合的put()方式不一樣,不同在於他們解決雜湊衝突的方式不同:集合Map的put()採用的是拉鍊法,而ThreadLocalMap的set()則是採用開放定址法;set()操作除了儲存元素外,還有就是replaceStaleEntry()和cleanSomeSlots(),這兩個方法可以清除掉key == null 的例項,防止記憶體洩漏;
3>getEntry()
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e);}
由於採用了開放定址法,所以當前key的雜湊值和元素在陣列的索引並不是完全對應的,首先取一個探測數(key的雜湊值),如果所對應的key就是我們所要找的元素,則返回,否則呼叫getEntryAfterMiss();
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null;}
當key == null時,呼叫了expungeStaleEntry()方法,該方法用於處理key == null,有利於GC回收,能夠有效地避免記憶體洩漏;
get():返回當前執行緒所對應的執行緒變數public T get() { // 獲取當前執行緒 Thread t = Thread.currentThread(); // 獲取當前執行緒的成員變數 threadLocal ThreadLocalMap map = getMap(t); if (map != null) { // 從當前執行緒的ThreadLocalMap獲取相對應的Entry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") // 獲取目標值 T result = (T)e.value; return result; } } return setInitialValue();}
首先通過當前執行緒獲取所對應的成員變數ThreadLocalMap,然後透過ThreadLocalMap獲取當前ThreadLocal的Entry,最後透過所獲取的Entry獲取目標值result;getMap()方法可以獲取當前執行緒所對應的ThreadLocalMap:
ThreadLocalMap getMap(Thread t) { return t.threadLocals;}
set(T value):設定當前執行緒的執行緒區域性變數的值
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value);}
獲取當前執行緒所對應的ThreadLocalMap,如果不為空,則呼叫ThreadLocalMap的set()方法,key就是當前ThreadLocal,如果不存在,則呼叫createMap()方法新建一個;
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue);}
initialValue():返回該執行緒區域性變數的初始值protected T initialValue() { return null;}
該方法定義為protected級別且返回為null,很明顯是要子類實現它的,所以我們在使用ThreadLocal的時候一般都應該覆蓋該方法;該方法不能顯示呼叫,只有在第一次呼叫get()或者set()方法時才會被執行,並且僅執行1次;
remove():將當前執行緒區域性變數的值刪除public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
該方法的目的是減少記憶體的佔用;當然,我們不需要顯示呼叫該方法,因為一個執行緒結束後,它所對應的區域性變數就會被垃圾回收;
(3)ThreadLocal記憶體洩漏
為什麼使用弱引用假如使用強引用,當ThreadLocal不再使用需要回收時,發現某個執行緒中ThreadLocalMap存在該ThreadLocal的強引用,無法回收,造成記憶體洩漏;因此,使用弱引用可以防止長期存在的執行緒(通常使用了執行緒池)導致ThreadLocal無法回收造成記憶體洩漏;為什麼弱引用還會記憶體洩漏在Entry物件中,雖然Key(ThreadLocal)是透過弱引用引入的,但是value即變數值本身是透過強引用引入;這就導致,假如不作任何處理,由於ThreadLocalMap和執行緒的生命週期是一致的,當執行緒資源長期不釋放,即使ThreadLocal本身由於弱引用機制已經回收掉了,但value還是駐留線上程的ThreadLocalMap的Entry中;即存在key為null,但value卻有值的無效Entry,導致記憶體洩漏;如何避免ThreadLocal中的expungeStaleEntry方法,擦除某個下標的Entry(置為null,可以回收),同時檢測整個Entry[]表中對key為null的Entry一併擦除,重新調整索引,該方法,在每次呼叫ThreadLocal的get、set、remove方法時都會執行;正確使用每次使用完ThreadLocal都呼叫它的remove()方法清除資料;將ThreadLocal變數定義成private static,這樣就一直存在ThreadLocal的強引用,也就能保證任何時候都能透過ThreadLocal的弱引用訪問到Entry的value值,進而清除掉;3)使用場景
當在一個類中使用 static 成員變數時,一定要考慮多個執行緒是否需要獨立的 static 成員變數,如果需要那就需要使用ThreadLocal
解決資料庫的連線//定義一個數據庫連線 private static Connection conn = null; private static ThreadLocal<Connection> connContainer = new ThreadLocal<Connection>(); //獲取連線 public synchronized static Connection getConnection() { //獲取連線物件 conn = connContainer.get(); try { if(conn == null) { Class.forName(DRIVER); conn = DriverManager.getConnection(URL, USER, PWD); connContainer.set(conn); } } catch (SQLException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } return conn; } //關閉連線 public static void closeConnection() { if(conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
儲存使用者sessionprivate static final ThreadLocal threadSession = new ThreadLocal();public static Session getSession() throws InfrastructureException { Session s = (Session) threadSession.get(); try { if (s == null) { s = getSessionFactory().openSession(); threadSession.set(s); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } return s;}
解決執行緒安全問題
public class DateUtil { private static ThreadLocal<SimpleDateFormat> format1 = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; public static String formatDate(Date date) { return format1.get().format(date); }}
Java7中的SimpleDateFormat不是執行緒安全的,可以用ThreadLocal來解決這個問題;