public class ThreadLocalTest1 implements Runnable{ private static ThreadLocal<Integer> threadLocalA = new ThreadLocal<Integer>(); private static ThreadLocal<Integer> threadLocalB = new ThreadLocal<Integer>(); public static void main(String[] args) { Thread t1 = new Thread(new ThreadLocalTest1(), "A"); Thread t2 = new Thread(new ThreadLocalTest1(), "B"); t1.start(); t2.start(); } @Override public void run() { threadLocalA.set((int) (Math.random() * 100D)); System.out.println("當前線程:" + Thread.currentThread().getName() + ": " + threadLocalA.get()); threadLocalB.set((int) (Math.random() * 100D)); System.out.println("當前線程:" + Thread.currentThread().getName() + ": " + threadLocalB.get()); } }
輸出:java
當前線程:A: 98
當前線程:A: 25
當前線程:B: 28
當前線程:B: 27mysql
其中: 98, 25 這兩個值都會放到本身所屬的線程對線A當中web
28, 27這兩個值都會放到本身所屬的線程對線A當中sql
ThreadLocal提供一個方便的方式,能夠根據不一樣的線程存放一些不一樣的特徵屬性,能夠方便的在線程中進行存取。 編程
歸納起來講,對於多線程資源共享的問題,同步機制採用了「以時間換空間」的方式,而 ThreadLocal 採用了「以空間換時間」的方式。前者僅提供一份變量,讓不一樣的線程排隊訪問;後者爲每個線程都提供了一份變量,所以能夠同時訪問而互不影響。數組
ThreadLocal 類主要有以下方法:緩存
protected T initialValue():設置初始值,默認爲null
public void set(T value):設置一個要保存的值,並會覆蓋原來的值
public T get():得到保存的值,若是沒有用過set方法,會獲取到初始值
public void remove():移除保存的值session
首先,它是一個數據結構,有點像HashMap,能夠保存"key : value"鍵值對,可是一個ThreadLocal只能保存一個,而且各個線程的數據互不干擾。數據結構
ThreadLocal<String> localName = new ThreadLocal(); localName.set("佔小狼"); String name = localName.get();
在線程1中初始化了一個ThreadLocal對象localName,並經過set方法,保存了一個值 佔小狼
,同時在線程1中經過 localName.get()
能夠拿到以前設置的值,可是若是在線程2中,拿到的將是一個null。多線程
這是爲何,如何實現?不過以前也說了,ThreadLocal保證了各個線程的數據互不干擾。
看看 set(T value)
和 get()
方法的源碼
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } 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(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
能夠發現,每一個線程中都有一個 ThreadLocalMap
數據結構,當執行set方法時,其值是保存在當前線程的 threadLocals
變量中,當執行set方法中,是從當前線程的 threadLocals
變量獲取。
因此在線程1中set的值,對線程2來講是摸不到的,並且在線程2中從新set的話,也不會影響到線程1中的值,保證了線程之間不會相互干擾。
那每一個線程中的 ThreadLoalMap
到底是什麼?
本文分析的是1.7的源碼。
從名字上看,能夠猜到它也是一個相似HashMap的數據結構,可是在ThreadLocal中,並沒實現Map接口。
在ThreadLoalMap中,也是初始化一個大小16的Entry數組,Entry對象用來保存每個key-value鍵值對,只不過這裏的key永遠都是ThreadLocal對象,是否是很神奇,經過ThreadLocal對象的set方法,結果把ThreadLocal對象本身當作key,放進了ThreadLoalMap中。
這裏須要注意的是,ThreadLoalMap的Entry是繼承WeakReference,和HashMap很大的區別是,Entry中沒有next字段,因此就不存在鏈表的狀況了。
沒有鏈表結構,那發生hash衝突了怎麼辦?
先看看ThreadLoalMap中插入一個key-value的實現
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
每一個ThreadLocal對象都有一個hash值 threadLocalHashCode
,每初始化一個ThreadLocal對象,hash值就增長一個固定的大小 0x61c88647
。
在插入過程當中,根據ThreadLocal對象的hash值,定位到table中的位置i,過程以下:一、若是當前位置是空的,那麼正好,就初始化一個Entry對象放在位置i上;二、不巧,位置i已經有Entry對象了,若是這個Entry對象的key正好是即將設置的key,那麼從新設置Entry中的value;三、很不巧,位置i的Entry對象,和即將設置的key不要緊,那麼只能找下一個空位置;
這樣的話,在get的時候,也會根據ThreadLocal對象的hash值,定位到table中的位置,而後判斷該位置Entry對象中的key是否和get的key一致,若是不一致,就判斷下一個位置
能夠發現,set和get若是衝突嚴重的話,效率很低,由於ThreadLoalMap是Thread的一個屬性,因此即便在本身的代碼中控制了設置的元素個數,但仍是不能控制其它代碼的行爲。
ThreadLocal可能致使內存泄漏,爲何?先看看Entry的實現:
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
經過以前的分析已經知道,當使用ThreadLocal保存一個value時,會在ThreadLocalMap中的數組插入一個Entry對象,按理說key-value都應該以強引用保存在Entry對象中,但在ThreadLocalMap的實現中,key被保存到了WeakReference對象中。
這就致使了一個問題,ThreadLocal在沒有外部強引用時,發生GC時會被回收,若是建立ThreadLocal的線程一直持續運行,那麼這個Entry對象中的value就有可能一直得不到回收,發生內存泄露。
既然已經發現有內存泄露的隱患,天然有應對的策略,在調用ThreadLocal的get()、set()可能會清除ThreadLocalMap中key爲null的Entry對象,這樣對應的value就沒有GC Roots可達了,下次GC的時候就能夠被回收,固然若是調用remove方法,確定會刪除對應的Entry對象。
若是使用ThreadLocal的set方法以後,沒有顯示的調用remove方法,就有可能發生內存泄露,因此養成良好的編程習慣十分重要,使用完ThreadLocal以後,記得調用remove方法。
ThreadLocal<String> localName = new ThreadLocal(); try { localName.set("佔小狼"); // 其它業務邏輯 } finally { localName.remove(); }
在web開發的session中,不一樣的線程對應不一樣的session,那麼如何針對不一樣的線程獲取對應的session呢?
咱們能夠設想了以下兩種方式:
1.在action中建立session,而後傳遞給Service,Service再傳遞給Dao,很明顯,這種方式將使代碼變得臃腫複雜。
2.建立一個靜態的map,鍵對應咱們的線程,值對應session,當咱們想獲取session時,只須要獲取map,而後根據當前的線程就能夠獲取對應的值。
咱們看看Hibernate中是如何實現這種狀況的:
在Hibernate中是經過使用ThreadLocal來實現的。在getSession方法中,若是ThreadLocal存在session,則返回session,不然建立一個session放入ThreadLocal中。
總結一下就是在ThreadLocal中存放了一個session。
實際上ThreadLocal中並無存聽任何的對象或引用,在上面的的代碼中ThreadLocal的實例threadSession只至關於一個標記的做用。而存放對象的真正位置是正在運行的Thread線程對象,每一個Thread對象中都存放着一個ThreadLocalMap類型threadLocals對象,這是一個映射表map,這個map的鍵是一個ThreadLocal對象,值就是咱們想存的局部對象。
咱們以上面的代碼爲例分析一下:
當咱們往ThreadLocal中存放變量的時候發生了什麼?
即這行代碼時。
咱們看下ThreadLocal的源碼中set()方法的實現。
若是把這些代碼簡化的話就一句
Thread.currentThread().threadLocals.set(this,value);
Thread.currentThread()獲取當前的線程
threadLocals就是咱們上面說的每一個線程對象中用於存放局部對象的map
因此set()就是獲取到當前線程的map而後把值放進去,咱們發現鍵是this,也就是當前的ThreadLocal對象,能夠發現ThreadLocal對象就是一個標記的做用,咱們根據這個標記找到對應的局部對象。
若是對比get()方法,能夠發現原理都差很少,都是對線程中的threadLocals這個map的操做,我就不解釋了。
ThreadLocal就是一個標記的做用,當咱們在線程中使用ThreadLocal的set()或者get()方法時,實際上是在操做咱們線程自帶的threadLocals這個map,多個線程的時候天然就有多個map,這些map互相獨立,可是,這些map都是根據一個ThreadLocal對象(由於它是靜態的)來做爲鍵存放。
這樣能夠在多個線程中,每一個線程存放不同的變量,咱們經過一個ThreadLocal對象,在不一樣的線程(經過Thread.currentThread()獲取當前線程)中獲得不一樣的值(不一樣線程的threadLocals不同)。
爲何threadLocals要是一個map呢?
由於咱們可能會在一個類中聲明多個ThreadLocal的實例,這樣就有多個標記,因此要使用map對應。
線程共享變量緩存以下:
Thread.ThreadLocalMap<ThreadLocal, Object>;
一、Thread: 當前線程,能夠經過Thread.currentThread()獲取。
二、ThreadLocal:咱們的static ThreadLocal變量。
三、Object: 當前線程共享變量。
咱們調用ThreadLocal.get方法時,其實是從當前線程中獲取ThreadLocalMap<ThreadLocal, Object>,而後根據當前ThreadLocal獲取當前線程共享變量Object。
ThreadLocal.set,ThreadLocal.remove其實是一樣的道理。
這種存儲結構的好處:
一、線程死去的時候,線程共享變量ThreadLocalMap則銷燬。
二、ThreadLocalMap<ThreadLocal,Object>鍵值對數量爲ThreadLocal的數量,通常來講ThreadLocal數量不多,相比在ThreadLocal中用Map<Thread, Object>鍵值對存儲線程共享變量(Thread數量通常來講比ThreadLocal數量多),性能提升不少。
關於ThreadLocalMap<ThreadLocal, Object>弱引用問題:
當線程沒有結束,可是ThreadLocal已經被回收,則可能致使線程中存在ThreadLocalMap<null, Object>的鍵值對,形成內存泄露。(ThreadLocal被回收,ThreadLocal關聯的線程共享變量還存在)。
雖然ThreadLocal的get,set方法能夠清除ThreadLocalMap中key爲null的value,可是get,set方法在內存泄露後並不會必然調用,因此爲了防止此類狀況的出現,咱們有兩種手段。
一、使用完線程共享變量後,顯示調用ThreadLocalMap.remove方法清除線程共享變量;
二、JDK建議ThreadLocal定義爲private static,這樣ThreadLocal的弱引用問題則不存在了。
(1)在線程中存放一些就像session的這種特徵變量,會針對不一樣的線程,有不一樣的值。
(2)通常 ThreadLocal 做爲全局變量使用,示例以下:
public class ConnectionManager { // 線程內共享Connection,ThreadLocal一般是全局的,支持泛型 private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>(); public static Connection getCurrentConnection() { Connection conn = threadLocal.get(); try { if(conn == null || conn.isClosed()) { String url = "jdbc:mysql://localhost:3306/test" ; conn = DriverManager.getConnection(url , "root" , "root") ; threadLocal.set(conn); } } catch (SQLException e) { } return conn; } public static void close() { Connection conn = threadLocal.get(); try { if(conn != null && !conn.isClosed()) { conn.close(); threadLocal.remove(); conn = null; } } catch (SQLException e) { } } }
(3)ThreadLocal解決共享參數
ThreadLocal 也經常使用在多線程環境中,某個方法處理一個業務,須要遞歸依賴其餘方法時,而要在這些方法中共享參數的問題。
例若有方法 a(),在該方法中調用了方法b(),而在方法 b() 中又調用了方法 c(),即 a–>b—>c。若是 a,b,c 如今都須要使用一個字符串參數 args。
經常使用的作法是 a(String args)–>b(String args)—c(String args)。可是使用ThreadLocal,能夠用另一種方式解決:
在某個接口中定義一個ThreadLocal 對象
方法 a()、b()、c() 所在的類實現該接口
在方法 a()中,使用 threadLocal.set(String args) 把 args 參數放入 ThreadLocal 中
方法 b()、c() 能夠在不用傳參數的前提下,在方法體中使用 threadLocal.get() 方法就能夠獲得 args 參數
示例以下:
interface ThreadArgs { ThreadLocal threadLocal = new ThreadLocal(); } class A implements ThreadArgs { public static void a(String args) { threadLocal.set(args); } } class B implements ThreadArgs { public static void b() { System.out.println(threadLocal.get()); } } class C implements ThreadArgs { public static void c() { System.out.println(threadLocal.get()); } } public class ThreadLocalDemo { public static void main(String[] args) { A.a(「hello」); B.b(); C.c(); } }
輸出結果:
hello hello
關於 InheritableThreadLocal
InheritableThreadLocal 類是 ThreadLocal 類的子類。ThreadLocal 中每一個線程擁有它本身的值,與 ThreadLocal 不一樣的是,InheritableThreadLocal 容許一個線程以及該線程建立的全部子線程均可以訪問它保存的值。
跟詳細的關於ThreadLocal的分析見這一篇: ThreadLocal的實現原理
ThreadLocal就是用來在類中聲明的一個標記,而後經過這個標記就根據不一樣Thread對象存取值。