解決ThreadLocal獲取不到值大坑
1:?jiǎn)栴}起因
今天項(xiàng)目上測(cè)試環(huán)境,再給領(lǐng)導(dǎo)演示的時(shí)候出現(xiàn)了bug,很尷尬。于是我跟前端同學(xué)通過(guò)模擬請(qǐng)求,最后發(fā)現(xiàn)在調(diào)一個(gè)接口的時(shí)候返回了一個(gè) token為空 的錯(cuò)誤。
但是前端同學(xué)說(shuō)傳了token了,那為什么還會(huì)報(bào)token為空的錯(cuò)誤呢
我們項(xiàng)目使用的JWT生成用戶token,每次請(qǐng)求都要經(jīng)過(guò)攔截器校驗(yàn)。因?yàn)樵谡?qǐng)求的時(shí)候我們經(jīng)常需要用到當(dāng)前用戶登錄的ID,所以我們使用到了ThhreadLocal這個(gè)工具類。
Map<String, Claim> claimMap = JwtUtil.verifyToken(headerToken);
if (null == claimMap) {
throw new GlobalException(ResponseEnums.TOKEN_INVALID_ERROR);
}
//將參數(shù)放入上下文中
Map<String, Object> result = new HashMap<>();
Set<Map.Entry<String, Claim>> entrySet = claimMap.entrySet();
for (Map.Entry<String, Claim> claimEntry : entrySet) {
result.put(claimEntry.getKey(), claimEntry.getValue().asString());
}
//將用戶ID存到ThreadLocal中,以便后續(xù)的獲取使用
ThreadLocalUtil.getInstance().setContext(result);這里也貼上ThreadLocalUtil工具類代碼
@Slf4j
public class ThreadLocalUtil {
private static final ThreadLocal<Map<String, Object>> CONTEXT = new ThreadLocal<>();
private ThreadLocalUtil() {}
public static ThreadLocalUtil getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final ThreadLocalUtil INSTANCE = new ThreadLocalUtil();
}
public void setContext(Map<String, Object> map) {
CONTEXT.set(map);
}
public static Map<String, Object> getContext() {
return CONTEXT.get();
}
public void clear() {
CONTEXT.remove();
}
public static Integer getUserId() {
Map<String, Object> context = getContext();
if(context == null || !context.containsKey(JwtUtil.USER_ID)) {
throw new GlobalException(ResponseEnums.TOKEN_IS_NULL_ERROR);
}
return Integer.parseInt(String.valueOf(context.get(JwtUtil.USER_ID)));
}
}2:?jiǎn)栴}復(fù)現(xiàn)
我們寫一個(gè)簡(jiǎn)單的測(cè)試
public static void main(String[] args) {
HashMap<String,Object> map = new HashMap<>();
map.put(JwtUtil.USER_ID,"1");
ThreadLocalUtil.getInstance().setContext(map);
System.out.println(ThreadLocalUtil.getUserId());
}
可以看到可以拿到我們?cè)O(shè)置的值。
但是如果將ThreadLocal跟Java8的Stream一起配合使用呢?
public static void main(String[] args) {
HashMap<String,Object> map = new HashMap<>();
map.put(JwtUtil.USER_ID,"1");
ThreadLocalUtil.getInstance().setContext(map);
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//ThreadLocal獲取值放在stream里面執(zhí)行
list.parallelStream().filter(x -> x.equals(ThreadLocalUtil.getUserId())).collect(Collectors.toList());
}
我們的問(wèn)題復(fù)現(xiàn)了,報(bào)了token為空的錯(cuò)誤。但是這還是隨機(jī)會(huì)出現(xiàn)的情況,并不是每次都會(huì)出現(xiàn)。所以導(dǎo)致我們?cè)谡{(diào)試的時(shí)候并沒(méi)有出現(xiàn)這個(gè)問(wèn)題
3:分析問(wèn)題
咋一看并不能知道為什么會(huì)這樣,所以我在獲取用戶id的打印了一下日志

看出問(wèn)題了吧,竟然有三個(gè)線程來(lái)獲取,因?yàn)槲覀冊(cè)O(shè)置值的線程就是main線程,所以前面二個(gè)線程獲取到的值就是空的,所以就拋出了異常

所以現(xiàn)在只需要知道這個(gè) ForkJoinPool是在哪就好了,最終在翻看源碼,找到原來(lái)就是在jdk8的Stream里面。
這是為什么呢?因?yàn)镴dk8的Stream底層使用了ForkJoinPool線程池,這就導(dǎo)致當(dāng)我們調(diào)用 ThreadLocalUtil.getUserId()的時(shí)候,是直接提交到了ForkJoinPool線程池中去了,這時(shí)候就會(huì)有其它線程去調(diào)用這個(gè)方法,所以就拿不到值了
4:如何解決
解決辦法就很簡(jiǎn)單了,只需要把ThreadLocalUtil.getUserId()單獨(dú)拿出來(lái)執(zhí)行就可以了
public static void main(String[] args) {
HashMap<String,Object> map = new HashMap<>();
map.put(JwtUtil.USER_ID,"1");
ThreadLocalUtil.getInstance().setContext(map);
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//單獨(dú)拿出來(lái)執(zhí)行
Integer userId = ThreadLocalUtil.getUserId();
list.parallelStream().filter(x -> x.equals(userId)).collect(Collectors.toList());
}
所以當(dāng)你項(xiàng)目使用到了ThreadLocal的時(shí)候,切記要單獨(dú)使用,否則指不定就出現(xiàn)跟我一樣的問(wèn)題了
以上就是解決ThreadLocal獲取不到值大坑的詳細(xì)內(nèi)容,更多關(guān)于ThreadLocal獲取不到值解決的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java反射的應(yīng)用之動(dòng)態(tài)代理深入理解
這篇文章主要介紹了Java反射的應(yīng)用之動(dòng)態(tài)代理深入理解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09
java讀取http請(qǐng)求中的body實(shí)例代碼
下面小編就為大家?guī)?lái)一篇java讀取http請(qǐng)求中的body實(shí)例代碼。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-09-09
Java經(jīng)典面試題匯總:網(wǎng)絡(luò)編程
本篇總結(jié)的是Java 網(wǎng)絡(luò)編程相關(guān)的面試題,后續(xù)會(huì)持續(xù)更新,希望我的分享可以幫助到正在備戰(zhàn)面試的實(shí)習(xí)生或者已經(jīng)工作的同行,如果發(fā)現(xiàn)錯(cuò)誤還望大家多多包涵,不吝賜教,謝謝2021-07-07
ActiveMQ中consumer的消息確認(rèn)機(jī)制詳解
這篇文章主要介紹了ActiveMQ中consumer的消息確認(rèn)機(jī)制詳解,對(duì)于broker而言,只有接收到確認(rèn)指令,才會(huì)認(rèn)為消息被正確的接收或者處理成功了,InforSuiteMQ提供以下幾種Consumer與Broker之間的消息確認(rèn)方式,需要的朋友可以參考下2023-10-10
SpringBoot接口實(shí)現(xiàn)百萬(wàn)并發(fā)的代碼示例
隨著互聯(lián)網(wǎng)的發(fā)展,越來(lái)越多的應(yīng)用需要支持高并發(fā),在這種情況下,如何實(shí)現(xiàn)高并發(fā)成為了一個(gè)重要的問(wèn)題,Spring Boot是一個(gè)非常流行的Java框架,它提供了很多方便的功能來(lái)支持高并發(fā),本文將介紹如何使用Spring Boot來(lái)實(shí)現(xiàn)百萬(wàn)并發(fā)2023-10-10
Dom4j解析XML_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了Dom4j解析XML,dom4j是一個(gè)Java的XML API,類似于jdom,用來(lái)讀寫XML文件的,有興趣的可以了解一下2017-07-07

