感謝博主的這篇分享,見 https://www.cnblogs.com/qifenghao/p/8977378.html
在今天的面試中,突然被考官問了這個問題,當時脫口而出的是 threadlocal容易會有內存泄漏,需要注意remove。其實自己仔細想想,這個回答太過於結果了,沒有思考為何要配合線程池的時候,去remove。
注意,這里需要你的jdk版本為1.8及以上,否者清將lambda表達式改為匿名內部類
問題的版本
1 public class ThreadLocalAndPool { 2 3 /** 4 * jdk8 的語法 5 */ 6 private static ThreadLocal<Integer> variableLocal = ThreadLocal.withInitial(() -> 0); 7 8 public static int get() { 9 return variableLocal.get(); 10 } 11 12 public static void remove() { 13 variableLocal.remove(); 14 } 15 16 public static void increment() { 17 variableLocal.set(variableLocal.get() + 1); 18 } 19 20 public static void main(String[] args) { 21 ExecutorService executorService = new ThreadPoolExecutor(2,2,0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(12)); 22 23 for(int i=0;i<5;i++){ 24 executorService.execute(()->{ 25 long threadId = Thread.currentThread().getId(); 26 27 int before = get(); 28 increment(); 29 int after = get(); 30 System.out.println("threadid " + threadId +" before " + before + ", after " + after); 31 }); 32 } 33 34 executorService.shutdown(); 35 } 36 37 38 }
得到的結果
threadid 12 before 0, after 1
threadid 13 before 0, after 1
threadid 12 before 1, after 2
threadid 13 before 1, after 2
threadid 12 before 2, after 3
這個其實就是threadlocal與線程池使用的問題了,因為threadlocal維護是 Map<Thread,T>這個結構,而線程池是對線程進行復用的,如果沒有及時的清理,那么之前對該線程的使用,就會影響到后面的線程了,造成數據不准確。
修正的版本,就是加一個remove
1 public class ThreadLocalAndPool { 2 3 /** 4 * jdk8 的語法 5 */ 6 private static ThreadLocal<Integer> variableLocal = ThreadLocal.withInitial(() -> 0); 7 8 public static int get() { 9 return variableLocal.get(); 10 } 11 12 public static void remove() { 13 variableLocal.remove(); 14 } 15 16 public static void increment() { 17 variableLocal.set(variableLocal.get() + 1); 18 } 19 20 public static void main(String[] args) { 21 ExecutorService executorService = new ThreadPoolExecutor(2,2,0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(12)); 22 23 for(int i=0;i<5;i++){ 24 executorService.execute(()->{ 25 try { 26 long threadId = Thread.currentThread().getId(); 27 28 int before = get(); 29 increment(); 30 int after = get(); 31 System.out.println("threadid " + threadId +" before " + before + ", after " + after); 32 } 33 finally { 34 remove(); 35 } 36 }); 37 } 38 39 executorService.shutdown(); 40 } 41 42 43 }
上面運行的結果如下(不同機器的threadid會有所不同)
threadid 12 before 0, after 1
threadid 13 before 0, after 1
threadid 12 before 0, after 1
threadid 13 before 0, after 1
threadid 13 before 0, after 1