什么是ThreadLocal?
ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
測試代碼:
package com.javaBase.LineDistance; /** * 〈一句話功能簡述〉; * 〈功能詳細描述〉 * * @author jxx * @see [相關類/方法](可選) * @since [產品/模塊版本] (可選) */ public class TestThreadLocal { public static void main(String[] args) { ThreadLocal<Integer> threadLocal = new MyThreadLocal(); Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 3; i++) { threadLocal.set(threadLocal.get() + 1); System.out.println("線程1:" + threadLocal.get()); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 3; i++) { threadLocal.set(threadLocal.get() + 1); System.out.println("線程2:" + threadLocal.get()); } } }); Thread t3 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 3; i++) { threadLocal.set(threadLocal.get() + 1); System.out.println("線程3:" + threadLocal.get()); } } }); t1.start(); t2.start(); t3.start(); } private static class MyThreadLocal extends ThreadLocal<Integer> { @Override protected Integer initialValue() { return 0; } } }
執行結果:
線程2:1 線程1:1 線程2:2 線程3:1 線程1:2 線程3:2 線程2:3 線程3:3 線程1:3
有結果可知個線程之間對ThreadLocal的操作互不影響。
ThreadLocal原理
ThreadLocal中的幾個主要方法:
- void set(Object value)設置當前線程的線程局部變量的值。
- public Object get()該方法返回當前線程所對應的線程局部變量。
- public void remove()將當前線程局部變量的值刪除,目的是為了減少內存的占用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束后,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量並不是必須的操作,但它可以加快內存回收的速度。
- protected Object initialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,並且僅執行1次。ThreadLocal中的缺省實現直接返回一個null。
get和set方法源碼:
/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ 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(); } /** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ 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這個類。ThreadLocalMap是ThreadLocal類的一個靜態內部類,它實現了鍵值對的設置和獲取(對比Map對象來理解),每個線程中都有一個獨立的ThreadLocalMap副本,它所存儲的值,只能被當前線程讀取和修改。ThreadLocal類通過操作每一個線程特有的ThreadLocalMap副本,從而實現了變量訪問在不同線程中的隔離。因為每個線程的變量都是自己特有的,完全不會有並發錯誤。還有一點就是,ThreadLocalMap存儲的鍵值對中的鍵是this對象指向的ThreadLocal對象,而值就是你所設置的對象了。
ThreadLocal的應用場景
1、方便同一個線程使用某一對象,避免不必要的參數傳遞;
2、線程間數據隔離(每個線程在自己線程里使用自己的局部變量,各線程間的ThreadLocal對象互不影響);
3、獲取數據庫連接、Session、關聯ID(比如日志的uniqueID,方便串起多個日志);
其中spring中的事務管理器就是使用的ThreadLocal:
Spring的事務管理器通過AOP切入業務代碼,在進入業務代碼前,會依據相應的事務管理器提取出相應的事務對象,假如事務管理器是DataSourceTransactionManager,
就會從DataSource中獲取一個連接對象,通過一定的包裝后將其保存在ThreadLocal中。而且Spring也將DataSource進行了包裝,重寫了當中的getConnection()方法,或者說
該方法的返回將由Spring來控制,這樣Spring就能讓線程內多次獲取到的Connection對象是同一個。
參考鏈接:徹底理解ThreadLocal