首先,ThreadLocal 不是用來解決共享對象的多線程訪問問題的,通常狀況下,經過ThreadLocal.set() 到線程中的對象是該線程本身使用的對象,其餘線程是不須要訪問的,也訪問不到的。各個線程中訪問的是不一樣的對象。
另外,說ThreadLocal使得各線程可以保持各自獨立的一個對象,並非經過ThreadLocal.set()來實現的,而是經過每一個線程中的new 對象 的操做來建立的對象,每一個線程建立一個,不是什麼對象的拷貝或副本。經過ThreadLocal.set()將這個新建立的對象的引用保存到各線程的本身的一個map中,每一個線程都有這樣一個map,執行ThreadLocal.get()時,各線程從本身的map中取出放進去的對象,所以取出來的是各自本身線程中的對象,ThreadLocal實例是做爲map的key來使用的。
若是ThreadLocal.set()進去的東西原本就是多個線程共享的同一個對象,那麼多個線程的ThreadLocal.get()取得的仍是這個共享對象自己,仍是有併發訪問問題。
下面來看一個hibernate中典型的ThreadLocal的應用: 算法
private 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; }
能夠看到在getSession()方法中,首先判斷當前線程中有沒有放進去session,若是尚未,那麼經過sessionFactory().openSession()來建立一個session,再將session set到線程中,實際是放到當前線程的ThreadLocalMap這個map中,這時,對於這個session的惟一引用就是當前線程中的那個ThreadLocalMap(下面會講到),而threadSession做爲這個值的key,要取得這個session能夠經過threadSession.get()來獲得,裏面執行的操做實際是先取得當前線程中的ThreadLocalMap,而後將threadSession做爲key將對應的值取出。這個session至關於線程的私有變量,而不是public的。 session
試想若是不用ThreadLocal怎麼來實現呢?可能就要在action中建立session,而後把session一個個傳到service和dao中,這可夠麻煩的。或者能夠本身定義一個靜態的map,將當前thread做爲key,建立的session做爲值,put到map中,應該也行,這也是通常人的想法,但事實上,ThreadLocal的實現恰好相反,它是在每一個線程中有一個map,而將ThreadLocal實例做爲key,這樣每一個map中的項數不多,並且當線程銷燬時相應的東西也一塊兒銷燬了,不知道除了這些還有什麼其餘的好處。 多線程
總之,ThreadLocal不是用來解決對象共享訪問問題的,而主要是提供了保持對象的方法和避免參數傳遞的方便的對象訪問方式。概括了兩點:
1。每一個線程中都有一個本身的ThreadLocalMap類對象,能夠將線程本身的對象保持到其中,各管各的,線程能夠正確的訪問到本身的對象。
2。將一個共用的ThreadLocal靜態實例做爲key,將不一樣對象的引用保存到不一樣線程的ThreadLocalMap中,而後在線程執行的各處經過這個靜態ThreadLocal實例的get()方法取得本身線程保存的那個對象,避免了將這個對象做爲參數傳遞的麻煩。併發
固然若是要把原本線程共享的對象經過ThreadLocal.set()放到線程中也能夠,能夠實現避免參數傳遞的訪問方式,可是要注意get()到的是那同一個共享對象,併發訪問問題要靠其餘手段來解決。但通常來講線程共享的對象經過設置爲某類的靜態變量就能夠實現方便的訪問了,彷佛不必放到線程中。
ThreadLocal的應用場合,我以爲最適合的是按線程多實例(每一個線程對應一個實例)的對象的訪問,而且這個對象不少地方都要用到。 this
固然ThreadLocal並不能替代同步機制,二者面向的問題領域不一樣。同步機制是爲了同步多個線程對相同資源的併發訪問,是爲了多個線程之間進行通訊的有效方式;而ThreadLocal是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源(變量),這樣固然不須要對多個線程進行同步了。因此,若是你須要進行多個線程之間進行通訊,則使用同步機制;若是須要隔離多個線程之間的共享衝突,可使用ThreadLocal,這將極大地簡化咱們的程序,使程序更加易讀、簡潔。ThreadLocal類爲各線程提供了存放局部變量的場所。 spa
JDK中ThreadLocal的實現:hibernate
並不是在ThreadLocal中有一個Map,而是在每一個Thread中存在這樣一個Map,具體是ThreadLocal.ThreadLocalMap。當用set時候,往當前線程裏面的Map裏 put 的key是當前的ThreadLocal對象。而不是把當前Thread做爲Key值put到ThreadLocal中的Map裏。 線程
public class ThreadLocal<T> { private final int threadLocalHashCode = nextHashCode(); private static int nextHashCode = 0; private static final int HASH_INCREMENT = 0x61c88647;
private static synchronized int nextHashCode() { int h = nextHashCode; nextHashCode = h + HASH_INCREMENT; return h; }
public ThreadLocal() { } public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) return (T)map.get(this); // Maps are constructed lazily. if the map for this thread // doesn't exist, create it, with this ThreadLocal and its // initial value as its only entry. T value = initialValue(); createMap(t, value); return 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 getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } .......
static class ThreadLocalMap { ........ } }
ThreadLocal內存泄漏:code
每一個Thread實例都具有一個ThreadLocal的map,以ThreadLocal Instance爲key,以綁定的Object爲Value。而這個map不是普通的map,它是在ThreadLocal中定義的,它和普通map的最大區別就是它的Entry是針對ThreadLocal弱引用的,即當外部ThreadLocal引用爲空時,map就能夠把ThreadLocal交給GC回收,從而獲得一個null的key。
這個threadlocal內部的map在Thread實例內部維護了ThreadLocal Instance和bind value之間的關係,這個map有threshold,當超過threshold時,map會首先檢查內部的ThreadLocal(前文說過,map是弱引用能夠釋放)是否爲null,若是存在null,那麼釋放引用給gc,這樣保留了位置給新的線程。若是不存在slate threadlocal,那麼double threshold。除此以外,還有兩個機會釋放掉已經廢棄的threadlocal佔用的內存,一是當hash算法獲得的table index恰好是一個null key的threadlocal時,直接用新的threadlocal替換掉已經廢棄的。另外每次在map中新建一個entry時(即沒有和用過的或未清理的entry命中時),會調用cleanSomeSlots來遍歷清理空間。此外,當Thread自己銷燬時,這個map也必定被銷燬了(map在Thread以內),這樣內部全部綁定到該線程的ThreadLocal的Object Value由於沒有引用繼續保持,因此被銷燬。
從上能夠看出Java已經充分考慮了時間和空間的權衡,可是由於置爲null的threadlocal對應的Object Value沒法及時回收。map只有到達threshold時或添加entry時才作檢查,不似gc是定時檢查,不過咱們能夠手工輪詢檢查,顯式調用map的remove方法,及時的清理廢棄的threadlocal內存。須要說明的是,只要不往不用的threadlocal中放入大量數據,問題不大,畢竟還有回收的機制。
綜上,廢棄threadlocal佔用的內存會在3中狀況下清理:
1 thread結束,那麼與之相關的threadlocal value會被清理
2 GC後,thread.threadlocals(map) threshold超過最大值時,會清理
3 GC後,thread.threadlocals(map) 添加新的Entry時,hash算法沒有命中既有Entry時,會清理
那麼什麼時候會「內存泄露」?當Thread長時間不結束,存在大量廢棄的ThreadLocal,而又再也不添加新的ThreadLocal(或新添加的ThreadLocal剛好和一個廢棄ThreadLocal在map中命中)時。對象