目錄java
建立一個線程本地變量。程序員
返回此線程局部變量的當前線程副本中的值,若是這是線程第一次調用該方法,則建立並初始化此副本。數據庫
返回此線程局部變量的當前線程的初始值。最多在每次訪問線程來得到每一個線程局部變量時調用此方法一次,即線程第一次使用 get() 方法訪問變量的時候。若是線程先於 get 方法調用 set(T) 方法,則不會在線程中再調用 initialValue 方法。api
若該實現只返回 null;若是程序員但願將線程局部變量初始化爲 null 之外的某個值,則必須爲 ThreadLocal 建立子類,並重寫此方法。一般,將使用匿名內部類。initialValue 的典型實現將調用一個適當的構造方法,並返回新構造的對象。安全
移除此線程局部變量的值。這可能有助於減小線程局部變量的存儲需求。session
將此線程局部變量的當前線程副本中的值設置爲指定值。多線程
public class Main { public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(){ @Override public void run(){ System.out.println("當前線程: " + Thread.currentThread().getName() + ", 已分配id: " + ThreadId.get()); } }.start(); } } static class ThreadId{ private static final AtomicInteger id = new AtomicInteger(0); private static final ThreadLocal<Integer> local = new ThreadLocal<>(){ @Override protected Integer initialValue(){ return id.getAndIncrement(); } }; // 返回當前線程的惟一的序列,若是第一次get,會先調用initialValue,後面看源碼就瞭解了 public static int get(){ return local.get(); } } } 控制檯打印結果: 當前線程: Thread-2, 已分配id: 1 當前線程: Thread-0, 已分配id: 2 當前線程: Thread-3, 已分配id: 4 當前線程: Thread-1, 已分配id: 0 當前線程: Thread-4, 已分配id: 3
public void set(T value) { Thread t = Thread.currentThread(); // 取當前線程 ThreadLocalMap map = getMap(t); // 和當前線程關聯的Map對象 if (map != null) { map.set(this, value); // this是當前ThreadLocal對象,將value映射到和當前線程相關的Map中 } else { createMap(t, value); // 不存在則建立 } }
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
能夠看到ThreadLocalMap是Thread對象中的一個屬性。每一個Thread對象都有一個ThreadLocal.ThreadLocalMap成員變量,ThreadLocal.ThreadLocalMap是一個ThreadLocal類的靜態內部類(以下所示),因此Thread類能夠進行引用.因此每一個線程都會有一個ThreadLocal.ThreadLocalMap對象的引用。併發
通俗的講,每個Thread對象都有一個ThreadLocal.ThreadLocalMap成員變量(做爲線程私有變量,從而也是線程安全的),而這些成員變量能夠代理給ThreadLocal進行管理。ide
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); // 若是Map中已經存在值,不論是set()方法設置,仍是已經初始化過,都再也不調用 }
對於ThreadLocal須要注意的有兩點:工具
/** * 數據庫鏈接管理類 */ public class ConnectionManager { /** 線程內共享Connection,ThreadLocal一般是全局的,支持泛型 */ private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>(); public static Connection getCurrConnection() { // 獲取當前線程內共享的Connection Connection conn = threadLocal.get(); try { // 判斷鏈接是否可用 if(conn == null || conn.isClosed()) { // 建立新的Connection賦值給conn(略) // 保存Connection threadLocal.set(conn); } } catch (SQLException e) { // 異常處理 } return conn; } /** * 關閉當前數據庫鏈接 */ public static void close() { // 獲取當前線程內共享的Connection Connection conn = threadLocal.get(); try { // 判斷是否已經關閉 if(conn != null && !conn.isClosed()) { // 關閉資源 conn.close(); // 移除Connection threadLocal.remove(); conn = null; } } catch (SQLException e) { // 異常處理 } } }
public class HibernateUtil { private static Log log = LogFactory.getLog(HibernateUtil.class); private static final SessionFactory sessionFactory; //定義SessionFactory static { try { // 經過默認配置文件hibernate.cfg.xml建立SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { log.error("初始化SessionFactory失敗!", ex); throw new ExceptionInInitializerError(ex); } } //建立線程局部變量session,用來保存Hibernate的Session public static final ThreadLocal session = new ThreadLocal(); /** * 獲取當前線程中的Session * @return Session * @throws HibernateException */ public static Session currentSession() throws HibernateException { Session s = (Session) session.get(); // 若是Session尚未打開,則新開一個Session if (s == null) { s = sessionFactory.openSession(); session.set(s); //將新開的Session保存到線程局部變量中 } return s; } public static void closeSession() throws HibernateException { //獲取線程局部變量,並強制轉換爲Session類型 Session s = (Session) session.get(); session.set(null); if (s != null) s.close(); } }
一張圖來看一下ThreadLocal對象以及和其相關的引用:
能夠看出,以用有兩個引用
ThreadLocal ---> 堆對象
Current Thread ---> Thread ---> ThreadLocalMap ---> Entry ---> ThreadLocal ---> 堆對象
ThreadLocalMap使用ThreadLocal的弱引用做爲key,若是一個ThreadLocal沒有外部強引用來引用它,那麼系統 GC 的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現key爲null的Entry,就沒有辦法訪問這些key爲null的Entry的value,若是當前線程再遲遲不結束的話,這些key爲null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠沒法回收,形成內存泄漏。其實,ThreadLocalMap的設計中已經考慮到這種狀況,也加上了一些防禦措施:在ThreadLocal的get(),set(),remove()的時候都會清除線程ThreadLocalMap裏全部key爲null的value。可是這些被動的預防措施並不能保證不會內存泄漏:
使用static的ThreadLocal,延長了ThreadLocal的生命週期,可能致使的內存泄漏。
分配使用了ThreadLocal又再也不調用get(),set(),remove()方法,那麼就會致使內存泄漏。
綜合上面的分析,咱們能夠理解ThreadLocal內存泄漏的來龍去脈,那麼怎麼避免內存泄漏呢?
每次使用完ThreadLocal,都調用它的remove()方法,清除數據。
在使用線程池的狀況下,沒有及時清理ThreadLocal,不只是內存泄漏的問題,更嚴重的是可能致使業務邏輯出現問題。因此,使用ThreadLocal就跟加鎖完要解鎖同樣,用完就清理。