又是一個風和日立的早上,這天小美遇到了一個難題:java
原來是小美在作服務鑑權的時候,須要根據每一個請求獲取token:數據庫
//獲取認證信息 Authenticationauthentication = tokenProvider.getAuthentication(jwt); //設置認證信息 SecurityContext.setAuthentication(authentication);
而後通過層層的調用,在業務代碼里根據認證信息進行權限的判斷,也就是鑑權。小美內心琢磨着,若是每一個方法參數中都傳遞SecurityContext信息,就顯的太過冗餘,並且看着也醜陋。那麼怎麼才能隱式傳遞參數呢?這個固然難不倒小美,她決定用ThreadLocal來傳遞這個變量:數組
classSecurityContextHolder{ private static final ThreadLocal<SecurityContext>contextHolder = newThreadLocal<SecurityContext>(); public SecurityContextgetContext(){ SecurityContextctx = contextHolder.get(); if(ctx==null){ contextHolder.set(createEmptyContext()); } returnctx; } }
......(省略沒必要要的)多線程
SecurityContextHolder.getContext().setAuthentication(authentication);ide
總體思路上就是將SecurityContext放入ThreadLocal,這樣當一個線程緣起生滅的時候,這個值會貫穿始終。
完美,小美喜滋滋的提交了代碼,而後發佈出去了。
結果次日系統就出現異常了,明明是這個用戶A的發起的請求,到了數據庫中,卻發現是操做人是用戶B的信息,一時間權限大亂。
完蛋了。。。spa
這是爲何呢?線程
咱們得先扯一扯ThreadLocal,Thread,ThreadLocalMap之間的愛恨情仇。設計
圖片解說:3d
因爲Thread內部的ThreadLocal.ThreadLocalMap對象是每一個線程私有的,因此作到了數據獨立。code
因而咱們知道了ThreadLocal是如何實現線程私有變量的。
可是問題來了,若是線程數不少,一直往ThreadLocalMap中存值,那內存豈不是要撐死了?
固然不是,設計者使用了弱引用來解決這個問題:
static class Entry extends WeakReference<ThreadLocal<?>>{ Object value; Entry(ThreadLocal<?> k,Object v){ super(k); value=v; } }
不過這裏的弱引用只是針對key。每一個key都弱引用指向ThreadLocal。當把ThreadLocal實例置爲null之後,沒有任何強引用指向ThreadLocal實例,因此ThreadLocal將會被GC回收。然而,value不能被回收,由於當前線程存在對value的強引用。只有當前線程結束銷燬後,強引用斷開,全部值纔將所有被GC回收,由此可推斷出,只有這個線程被回收了,ThreadLocal以及value纔會真正被回收。
聽起來很正常?
那若是咱們使用線程池呢?常駐線程不會被銷燬。這就完蛋了,ThreadLocal和value永遠沒法被GC回收,形成內存泄漏那是必然的。
而咱們的請求進入到系統時,並非一個請求生成一個線程,而是請求先進入到線程池,再由線程池調配出一個線程進行執行,執行完畢後放回線程池,這樣就會存在一個線程屢次被複用的狀況,這就產生了這個線程這次操做中獲取到了上次操做的值。
怎麼辦呢?
解決辦法就是每次使用完ThreadLocal對象後,都要調用其remove方法,清除ThreadLocal中的內容。
public class ThreadLocalTest{ static ThreadLocal<AtomicInteger> sequencer = ThreadLocal.withInitial(()->newAtomicInteger(0)); static class Task implements Runnable{ @Override public void run(){ int value = sequencer.get().getAndIncrement(); System.out.println("-------"+value); } } public static void main(String[]args){ ExecutorService executor = Executors.newFixedThreadPool(2); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.shutdown(); } }
輸出: 0 1 0 2 3 1
這個就是錯誤的,正確代碼以下:
public class ThreadLocalTest{ static ThreadLocal<AtomicInteger> sequencer = ThreadLocal.withInitial(()->newAtomicInteger(0)); static class Task implements Runnable{ @Override public void run(){ int value = sequencer.get().getAndIncrement(); System.out.println("-------"+value); sequencer.remove(); } } public static void main(String[]args){ ExecutorService executor = Executors.newFixedThreadPool(2); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.shutdown(); } }