ThreadLocal,線程局部變量。ThreadLocal經過爲每一個線程提供一個獨立的變量副本解決了變量併發訪問的衝突問題。在不少狀況下,ThreadLocal比直接使用synchronized同步機制解決線程安全問題更簡單,更方便,且結果程序擁有更高的併發性。java
在Java的多線程編程中,爲保證多個線程對共享變量的安全訪問,一般會使用synchronized來保證同一時刻只有一個線程對共享變量進行操做。這種狀況下能夠將類變量放到ThreadLocal類型的對象中,使變量在每一個線程中都有獨立拷貝,不會出現一個線程讀取變量時而被另外一個線程修改的現象。最多見的ThreadLocal使用場景爲用來解決數據庫鏈接、Session管理等。數據庫
由結構圖可知,ThreadLocal的一些核心機制:編程
所以對於不一樣的線程,每次獲取副本值時,別的線程並不能獲取到當前線程的副本值,造成了副本的隔離,互不干擾。安全
Thread線程內部的Map類以下:多線程
public class Thread implements Runnable { ...... // 與此線程有關的ThreadLocal值,由ThreadLocal類維護。 ThreadLocal.ThreadLocalMap threadLocals = null; ...... }
ThreadLocal類提供如下方法:併發
get()方法:用於獲取當前線程的副本變量值。dom
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; } private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
set()方法:用於保存當前線程的副本變量值。ide
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocal.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 ThreadLocal.ThreadLocalMap(this, firstValue); }
remove()方法:移除當前線程的副本變量值。this
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
ThreadLocalMap是ThreadLocal的內部類,沒有實現Map接口,用獨立的方式實現了Map的功能,其內部的Entry也獨立實現。線程
在ThreadLocalMap中,使用Entry來保存K-V結構數據。可是Entry中key只能是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; } }
ThreadLocalMap中使用的key爲ThreadLocal的弱引用,而value是強引用。因此,若是ThreadLocal沒有被外部強引用的狀況下,在垃圾回收時key會被清理掉而value不會被清理掉。這樣,ThreadLocalMap中就會出現key爲null的Entry。加入不作任何措施,value永遠沒法被GC回收,這種時候可能形成內存泄漏。
所以爲了不內存泄漏問題,在調用ThreadLocal的get()、set()方法時完成後再調用remove方法,將Entry節點和Map的引用關係移除,這樣整個Entry對象在GC Roots分析後就變成不可達了,下次GC的時候就能夠被回收。
ThreadLocal處理SimpleDateFormat線程不安全問題。
import java.text.SimpleDateFormat; import java.util.Random; /** * ThreadLocal處理SimpleDateFormat線程不安全問題 */ public class ThreadLocalDemo implements Runnable { private static final ThreadLocal<SimpleDateFormat> FORMATTER = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm")); public static void main(String[] args) throws InterruptedException { ThreadLocalDemo demo = new ThreadLocalDemo(); for (int i = 0; i < 10; i++) { Thread t = new Thread(demo, "" + i); Thread.sleep(new Random().nextInt(1000)); t.start(); } } @Override public void run() { System.out.println("Thread Name = " + Thread.currentThread().getName() + "; Default Formatter = " + FORMATTER.get().toPattern()); try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } FORMATTER.set(new SimpleDateFormat()); System.out.println("Thread Name = " + Thread.currentThread().getName() + "; Formatter = " + FORMATTER.get().toPattern()); } }
執行結果:
由輸出可知,Thread0已經改變了FORMATTER的值,但Thread1的初始化值仍然與默認初始化值相同,其餘線程也同樣。