ThreadLocal通常稱爲線程本地變量,它是一種特殊的線程綁定機制,將變量與線程綁定在一塊兒,爲每個線程維護一個獨立的變量副本。經過ThreadLocal能夠將對象的可見範圍限制在同一個線程內。java
跳出誤區web
須要重點強調的的是,不要拿ThreadLocal和synchronized作類比,由於這種比較壓根就是無心義的!sysnchronized是一種互斥同步機制,是爲了保證在多線程環境下對於共享資源的正確訪問。而ThreadLocal從本質上講,無非是提供了一個「線程級」的變量做用域,它是一種線程封閉(每一個線程獨享變量)技術,更直白點講,ThreadLocal能夠理解爲將對象的做用範圍限制在一個線程上下文中,使得變量的做用域爲「線程級」。spring
沒有ThreadLocal的時候,一個線程在其聲明週期內,可能穿過多個層級,多個方法,若是有個對象須要在此線程週期內屢次調用,且是跨層級的(線程內共享),一般的作法是經過參數進行傳遞;而ThreadLocal將變量綁定在線程上,在一個線程週期內,不管「你身處何地」,只需經過其提供的get方法就可輕鬆獲取到對象。極大地提升了對於「線程級變量」的訪問便利性。tomcat
來看個簡單的例子安全
假設咱們要爲每一個線程關聯一個惟一的序號,在每一個線程週期內,咱們須要屢次訪問這個序號,這時咱們就可使用ThreadLocal了.(固然下面這個例子沒有徹底體現出跨層級跨方法的調用,理解就能夠了)服務器
package concurrent; import java.util.concurrent.atomic.AtomicInteger; /** * Created by chengxiao on 2016/12/12. */ public class ThreadLocalDemo { public static void main(String []args){ for(int i=0;i<5;i++){ final Thread t = new Thread(){ @Override public void run(){ System.out.println("當前線程:"+Thread.currentThread().getName()+",已分配ID:"+ThreadId.get()); } }; t.start(); } } static class ThreadId{ //一個遞增的序列,使用AtomicInger原子變量保證線程安全 private static final AtomicInteger nextId = new AtomicInteger(0); //線程本地變量,爲每一個線程關聯一個惟一的序號 private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement();//至關於nextId++,因爲nextId++這種操做是個複合操做而非原子操做,會有線程安全問題(可能在初始化時就獲取到相同的ID,因此使用原子變量 } }; //返回當前線程的惟一的序列,若是第一次get,會先調用initialValue,後面看源碼就瞭解了 public static int get() { return threadId.get(); } } }
執行結果,能夠看到每一個線程都分配到了一個惟一的ID,同時在此線程範圍內的"任何地點",咱們均可以經過ThreadId.get()這種方式直接獲取。session
當前線程:Thread-4,已分配ID:1 當前線程:Thread-0,已分配ID:0 當前線程:Thread-2,已分配ID:3 當前線程:Thread-1,已分配ID:4 當前線程:Thread-3,已分配ID:2
set操做,爲線程綁定變量多線程
public void set(T value) { Thread t = Thread.currentThread();//1.首先獲取當前線程對象 ThreadLocalMap map = getMap(t);//2.獲取該線程對象的ThreadLocalMap if (map != null) map.set(this, value);//若是map不爲空,執行set操做,以當前threadLocal對象爲key,實際存儲對象爲value進行set操做 else createMap(t, value);//若是map爲空,則爲該線程建立ThreadLocalMap }
能夠看到,ThreadLocal不過是個入口,真正的變量是綁定在線程上的。併發
ThreadLocalMap getMap(Thread t) { return t.threadLocals;//線程對象持有ThreadLocalMap的引用 }
下面給是Thread類中的定義,每一個線程對象都擁有一個ThreadLocalMap對象ide
ThreadLocal.ThreadLocalMap threadLocals = null;
如今,咱們能看出ThreadLocal的設計思想了:
1.ThreadLocal僅僅是個變量訪問的入口;
2.每個Thread對象都有一個ThreadLocalMap對象,這個ThreadLocalMap持有對象的引用;
3.ThreadLocalMap以當前的threadlocal對象爲key,以真正的存儲對象爲value。get時經過threadlocal實例就能夠找到綁定在當前線程上的對象。
乍看上去,這種設計確實有些繞。咱們徹底能夠在設計成Map<Thread,T>這種形式,一個線程對應一個存儲對象。ThreadLocal這樣設計的目的主要有兩個:
一是能夠保證當前線程結束時相關對象能儘快被回收;二是ThreadLocalMap中的元素會大大減小,咱們都知道map過大更容易形成哈希衝突而致使性能變差。
咱們再來看看get方法
public T get() { Thread t = Thread.currentThread();//1.首先獲取當前線程 ThreadLocalMap map = getMap(t);//2.獲取線程的map對象 if (map != null) {//3.若是map不爲空,以threadlocal實例爲key獲取到對應Entry,而後從Entry中取出對象便可。 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue();//若是map爲空,也就是第一次沒有調用set直接get(或者調用過set,又調用了remove)時,爲其設定初始值 }
setInitialValue
1 private T setInitialValue() { 2 T value = initialValue();//獲取初始值 3 Thread t = Thread.currentThread(); 4 ThreadLocalMap map = getMap(t); 5 if (map != null) 6 map.set(this, value); 7 else 8 createMap(t, value); 9 return value; 10 }
initialValue方法,默認是null,訪問權限是protected,即容許重寫。
1 protected T initialValue() { 2 return null; 3 }
談到這兒,咱們應該已經對ThreadLocal的設計目的及設計思想有必定的瞭解了。
還有一個會引發疑惑的問題,咱們說ThreadLocal爲每個線程維護一個獨立的變量副本,那麼是否是說各個線程之間真正的作到對於對象的「徹底自治」而不對其餘線程的對象產生影響呢?其實這已經不屬於對於ThreadLocal的討論,而是你出於何種目的去使用ThreadLocal。若是咱們爲一個線程關聯的對象是「徹底獨享」的,也就是每一個線程擁有一整套的新的 棧中的對象引用+堆中的對象,那麼這種狀況下是真正的完全的「線程獨享變量」,至關於一種深度拷貝,每一個線程本身玩本身的,對該對象作任何的操做也不會對別的線程有任何影響。
另外一種更廣泛的狀況,所謂的獨享變量副本,其實也就是每一個線程都擁有一個獨立的對象引用,而堆中的對象仍是線程間共享的,這種狀況下,天然仍是會涉及到對共享資源的訪問操做,依然會有線程不安全的風險。因此說,ThreadLocal沒法解決線程安全問題。
因此,需不須要徹底獨享變量,進行徹底隔離,就取決於你的應用場景了。能夠想象,對象過大的時候,若是每一個線程都有這麼一份「深拷貝」,併發又比較大,對於服務器的壓力天然是很大的。像web開發中的servlet,servlet是線程不安全的,一請求一線程,多個線程共享一個servlet對象;而早期的CGI設計中,N個請求就對應N個對象,併發量大了以後性能天然就不好。
ThreadLocal在spring的事務管理,包括Hibernate的session管理等都有出現,在web開發中,有時會用來管理用戶會話 HttpSession,web交互中這種典型的一請求一線程的場景彷佛比較適合使用ThreadLocal,可是須要特別注意的是,因爲此時session與線程關聯,而tomcat這些web服務器多會採用線程池機制,也就是說線程是可複用的,因此在每一次進入的時候都須要從新進行set,或者在結束時及時remove。
做者: dreamcatcher-cx
出處: <http://www.cnblogs.com/chengxiao/>
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在頁面明顯位置給出原文連接。