ThreadLocal 稱為線程本地存儲,它為每一個使用它的線程提供一個其值(value)的副本。可以將 ThreadLocal<T> 理解成 Map<Thread, T>,即使用當前線程為 key 的一個 Map,ThreadLocal 的 get() 方法從 Map 里取本地變數(本地 valu ...
ThreadLocal 稱為線程本地存儲,一般作為靜態域使用,它為每一個使用它的線程提供一個其值(value)的副本。通常對資料庫連接(Connection)和事務(Transaction)使用線程本地存儲。
可以簡單地將 ThreadLocal<T> 理解成一個容器,它將 value 對象存儲在 Map<Thread, T> 域中,即使用當前線程為 key 的一個 Map,ThreadLocal 的 get() 方法從 Map 里取與當前線程相關聯的 value 對象。ThreadLocal 的真正實現並不是這樣的,但是可以簡單地這樣理解。
線程池中的線程在任務執行完成後會被覆用,所以線上程執行完成時,要對 ThreadLocal 進行清理(清除掉與本線程相關聯的 value 對象)。不然,被覆用的線程去執行新的任務時會使用被上一個線程操作過的 value 對象,從而產生不符合預期的結果。
下麵舉一個簡單的例子來說明:
1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 4 public class ThreadLocalVariableHolder { 5 private static ThreadLocal<Integer> variableHolder = new ThreadLocal<Integer>() { 6 @Override 7 protected Integer initialValue() { 8 return 0; 9 } 10 }; 11 12 public static int getValue() { 13 return variableHolder.get(); 14 } 15 16 public static void remove() { 17 variableHolder.remove(); 18 } 19 20 public static void increment() { 21 variableHolder.set(variableHolder.get() + 1); 22 } 23 24 public static void main(String[] args) { 25 ExecutorService executor = Executors.newCachedThreadPool(); 26 for (int i = 0; i < 5; i++) { 27 executor.execute(() -> { 28 int before = getValue(); 29 increment(); 30 int after = getValue(); 31 System.out.println("before: " + before + ", after: " + after); 32 }); 33 } 34 35 executor.shutdown(); 36 } 37 }
執行結果如下(如果你的執行結果 before 都是 0,after 都是 1 的話,就增加線程池執行的線程個數):
before: 0, after: 1 before: 0, after: 1 before: 0, after: 1 before: 1, after: 2 before: 1, after: 2
既然是為每個線程都提供一個副本,為什麼會出現 before 不為 0 的情況呢?
下麵追蹤每一個執行的線程,將 main 方法修改為如下:
1 public static void main(String[] args) { 2 ExecutorService executor = Executors.newCachedThreadPool(); 3 for (int i = 0; i < 5; i++) { 4 executor.execute(() -> { 5 long threadId = Thread.currentThread().getId(); 6 int before = getValue(); 7 increment(); 8 int after = getValue(); 9 System.out.println("threadId: " + threadId + ", before: " + before + ", after: " + after); 10 }); 11 } 12 13 executor.shutdown(); 14 }
執行結果如下:
threadId: 10, before: 0, after: 1 threadId: 11, before: 0, after: 1 threadId: 12, before: 0, after: 1 threadId: 12, before: 1, after: 2 threadId: 11, before: 1, after: 2
由上面的執行結果可以看出,id 為 11 和 12 的線程被覆用。線程池在復用線程執行任務時使用被之前的線程操作過的 value 對象。因此,在每個線程執行完成時,應該清理 ThreadLocal。具體做法如下:
1 public static void main(String[] args) { 2 ExecutorService executor = Executors.newCachedThreadPool(); 3 for (int i = 0; i < 100; i++) { 4 executor.execute(() -> { 5 try { 6 long threadId = Thread.currentThread().getId(); 7 int before = getValue(); 8 increment(); 9 int after = getValue(); 10 System.out.println("threadId: " + threadId + ", before: " + before + ", after: " + after); 11 } finally { 12 // 清理線程本地存儲 13 remove(); 14 } 15 }); 16 } 17 18 executor.shutdown(); 19 }