java中ThreadLocal的實現及原理

疑問:ThreadLocal能夠解決共享變量的併發問題?
一、概念:spring

ThreadLocal類用來存放線程的局部變量,每一個線程都有本身的局部變量彼此之間不共享。ThreadLocal<T>主要有一下三個方法:segmentfault

(1).public T get():返回當前線程的局部變量。緩存

(2).protected T initValue():返回當前線程的局部變量初始值。默認狀況下initValue(),返回null。線程在沒有調用set以前,第一次調用get的時候,get方法會默認去調用initValue這個方法,因此若是沒有覆蓋這個方法,可能致使get返回的是null。固然若是調用了set方法,結果就會不同了。安全

(3).public void set(T value):設置當前線程的局部變量。多線程

ThreadLocal是如何作到爲每個線程提供單獨的局部變量呢?實際在ThreadLocal類中有一個Map緩存,用於存儲每個線程的局部變量。Map中元素的鍵爲線程對象,而值對應線程的變量副本。併發

實現例子:ide

public class TestThreadLocal {編碼

private static final ThreadLocal<String> threadLocalA = new ThreadLocal<>(); private static final ThreadLocal<String> threadLocalB = new ThreadLocal<>();spa

/** * 在調用的線程的map中存入key爲ThreadLocal自己,value爲該線程設置的值 */ public static void setValueA(String value){ threadLocalA.set(value); } public static String getValueA(){ return threadLocalA.get(); } public static void clearValueA(){ threadLocalA.remove(); } public static void setValueB(String value){ threadLocalB.set(value); } public static String getValueB(){ return threadLocalB.get(); } public static void clearValueB(){ threadLocalB.remove(); }.net

public static void main(String args[]) { //線程1的ThreadLocalMap中存着key爲threadLocalA,value爲A1;key爲threadLocalB,value爲B1 new Thread(){ @Override public void run(){ setValueA("A1"); System.out.println("thread1:" + getValueA()); clearValueA();

setValueB("B1");
        System.out.println("thread1:" + getValueB());
        clearValueB();
     }
  }.start();

//線程2的ThreadLocalMap中存着key爲threadLocalA,value爲A2;key爲threadLocalB,value爲B2 new Thread(){ @Override public void run(){ setValueA("A2"); System.out.println("thread2:" + getValueA()); clearValueA();

setValueB("B2");
     System.out.println("thread2:" + getValueB());
     clearValueB();
  }

}.start(); } }

運行結果:

thread1:A1
thread1:B1
thread2:A2
thread2:B2

總結:從該例子中可看出多個線程設置ThreadLocal的值只有在該線程的做用範圍內有效。操做ThreadLocal的set,get方法其實是操做該線程的一個代理,其本質是在該線程的ThreadLocalMap中存儲了key爲ThreadLocal自己和對應的值。

隔離性:驗證線程變量的隔離性

public class ThreadA extends Thread { @Override public void run() { try { for (int i=0;i<100;i++) { ThreadLocalDemo.threadLocal.set("ThreadA:" + (i + 1)); System.out.println("ThreadLocal getValue:" + ThreadLocalDemo.threadLocal.get()); Thread.sleep(2000); } } catch (InterruptedException e) { e.printStackTrace(); } super.run(); } }

public class ThreadB extends Thread { @Override public void run() { try { for (int i=0;i<100;i++) { ThreadLocalDemo.threadLocal.set("Thread" + (i + 1)); System.out.println("ThreadA getValue" + ThreadLocalDemo.threadLocal.get()); Thread.sleep(2000); } } catch (InterruptedException e) { e.printStackTrace(); } super.run(); } }

public class ThreadLocalDemo { public static ThreadLocal<String> threadLocal = new ThreadLocal<>();

public static void main(String args[]){ ThreadA threadA = new ThreadA(); threadA.start();

ThreadB threadB = new ThreadB();
  threadB.start();
  try{
  for (int i=0;i<100;i++){
     threadLocal.set("Main"+(i+1));
     System.out.println("Main getValue" + threadLocal.get());
        Thread.sleep(2000);
  }
     } catch (InterruptedException e) {
        e.printStackTrace();
     }
  }

}

小結:從上面執行的結果能夠看出,每個線程向ThreadLocal中存值時,可是每一個線程取出的都是本身線程的值,這也就是驗證的線程變量的隔離性

ThreadLocal的幾個問題

一、爲何不直接用線程id來做爲ThreadLocalMap的key?

由於一個線程中能夠有多個ThreadLocal對象,因此ThreadLocalMap中能夠有多個鍵值對,存儲多個value值,而若是使用線程id做爲Key,那麼就只有一個鍵值對了。

二、ThreadLocal的內存泄露問題

首先理解內存泄漏(memory leak)和內存溢出(out of memory)的區別。內存溢出是由於在內存中建立了大量引用的對象,致使後續再申請內存時沒有足夠的內存空間供其使用,內存泄漏是指程序申請完內存後,沒法釋放已經申請的內存空間。(再也不使用的對象或者變量仍佔內存空間)。

根據Entry方法的源碼,咱們知道ThreadLocalMap是使用ThreadLocal的弱引用做爲key的。下圖是介紹到一些對象之間的引用關係圖,實線表示強引用,虛線表示弱引用。

如圖,ThreadLocalMap使用ThreadLocal弱引用做爲key,若是一個ThreadLocal沒有外部強引用引用他,那麼系統gc的時候,這個ThreadLocal勢必會被回收,這樣的話,ThreadLocalMap中就會出現key爲null的Entry,就沒有辦法訪問這些key爲null的Entry,若是當前線程遲遲不結束的話,這些key爲null的Entry的value就會一直存在一條強引用鏈。

Thread Ref ->Thread->ThreadLocalMap->Entry->value

內存申請完以後,永遠沒法回收,形成內存泄漏。只有當前thread結束之後,Thread Ref就不會存在棧中,強引用斷開,Thread,ThreadLocalMap,Entry將所有被GC回收。但若是是線程對象不被回收的狀況,好比使用線程池,線程結束是不會被銷燬的,就可能出現真正意義上的內存泄漏。

ThreadLocalMap設計時對上面問題的對策:

當咱們仔細研究ThreadLocalMap的源碼,能夠推斷,若是在使用ThreadLocal的過程當中,顯式的進行remove是個很好的編碼習慣,這樣是不會引發內存泄漏。

https://segmentfault.com/a/1190000015718453

三、ThreadLocal使用場景舉例

ThreadLocal既然不能解決併發問題,那麼它適用的場景是什麼呢?

ThreadLocal的主要用途是爲了保持線程自身對象和避免參數傳遞,主要適用場景是按線程多實例(每一個線程對應一個實例)的對象的訪問,而且這個對象不少地方都要用到。

{spring與ThreadLocal

spring中的bean大多都是單例的,可是咱們應用又是支持併發的,因此將各個service,dao存放在ThreadLocal中是一個明智的作法。可是須要注意若是bean中包含共享變量,spring是沒有作任何的安全處理的。

【synchronized與ThreadLocal

ThreadLocal以空間換取時間,提供了一種很是簡便的多線程實現方式。 
ThreadLocal和Synchonized都用於解決多線程併發訪問。可是ThreadLocal與synchronized有本質的區別:
synchronized是利用鎖的機制,使變量或代碼塊在某一時該只能被一個線程訪問。而ThreadLocal爲每個線程都提供了變量的副本。Synchronized用於線程間的數據共享,而ThreadLocal則用於線程間的數據隔離。

相關文章
相關標籤/搜索