1.ThreadLocal是Java1.2提出來的一種對線程的所在執行的線程棧的局部變量
這個方式打印出來的就可以說明一個問題,不同的線程他們的的線程棧是不一樣的,換句話當同一個方法被同一個不同的線程調用的時候,他們都會進入各自的線程之間的
棧內存之中。
public class Main { public static void main(String[] args) throws Exception { log("start main..."); new Thread(() -> { log("run task..."); }).start(); new Thread(() -> { log("print..."); }).start(); log("end main."); } static void log(String s) { System.out.println(Thread.currentThread().getName() + ": " + s); } }
現象:
main: start main... Thread-0: run task... main: end main. Thread-1: print...
2.ThreadLocal可以避免在同一個線程之間同一個參數的在不同地方調用(前提是同一個線程),這樣可以避免了同一個參數在同一個線程執行中的多次傳遞;ThreadLocal
實例通常總是以靜態字段初始化如下:
static ThreadLocal<User> threadLocalUser = new ThreadLocal<>();
它的典型使用方式如下:
void processUser(user) { try { threadLocalUser.set(user); step1(); step2(); } finally { threadLocalUser.remove(); }
通過設置一個User
實例關聯到ThreadLocal
中,在移除之前,所有方法都可以隨時獲取到該User
實例:
void step1() { User u = threadLocalUser.get(); log(); printUser(); } void log() { User u = threadLocalUser.get(); println(u.name); } void step2() { User u = threadLocalUser.get(); checkUser(u.id); }
注意到普通的方法調用一定是同一個線程執行的,所以,step1()
、step2()
以及log()
方法內,threadLocalUser.get()
獲取的User
對象是同一個實例。
實際上,可以把ThreadLocal
看成一個全局Map<Thread, Object>
:每個線程獲取ThreadLocal
變量時,總是使用Thread
自身作爲key:
Object threadLocalValue = threadLocalMap.get(Thread.currentThread());
因此,ThreadLocal
相當於給每個線程都開闢了一個獨立的存儲空間,各個線程的ThreadLocal
關聯的實例互不干擾。最後,特別注意ThreadLocal
一定要在finally
中清除:
try { threadLocalUser.set(user); ... } finally { threadLocalUser.remove(); }
這是因爲當前線程執行完相關代碼後,很可能會被重新放入線程池中,如果ThreadLocal
沒有被清除,該線程執行其他代碼時,會把上一次的狀態帶進去。爲了保證能釋放ThreadLocal
關聯的實例,我們可以通過AutoCloseable
接口配合try (resource) {...}
結構,讓編譯器自動爲我們關閉。例如,一個保存了當前用戶名的ThreadLocal
可以封裝爲一個UserContext
對象:
public class UserContext implements AutoCloseable { static final ThreadLocal<String> ctx = new ThreadLocal<>(); public UserContext(String user) { ctx.set(user); } public static String currentUser() { return ctx.get(); } @Override public void close() { ctx.remove(); } }