springboot 使用ThreadLocal的實例代碼
springboot 使用ThreadLocal
本文參考慕課教程給出一個在spring boot中使用ThreadLocal實現(xiàn)線程封閉的實例。
首先創(chuàng)建一個包含ThreadLocal成員變量的實例:
public class RequestHolder {
private final static ThreadLocal<Long> requestHolder = new ThreadLocal<>();
public static void add(Long id) {
requestHolder.set(id);
}
public static Long getId() {
return requestHolder.get();
}
public static void remove() {
requestHolder.remove();
}
}
編寫一個Controller類,請求該類的test()方法獲取ThreadLocal中存儲的id:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/threadLocal")
public class ThreadLocalController {
@RequestMapping("/test")
@ResponseBody
public Long test() {
return RequestHolder.getId();
}
}
編寫過濾器,在請求到達Servlet之前(請求->tomcat容器->filter->servlet->inteceptor->controller),將當(dāng)前線程的id添加到ThreadLocal中:
import com.mmall.concurrency.example.threadLocal.RequestHolder;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Slf4j
public class HttpFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
log.info("do filter, {}, {}", Thread.currentThread().getId(), request.getServletPath());
//在ThreadLocal中添加當(dāng)前線程的id
RequestHolder.add(Thread.currentThread().getId());
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
編寫攔截器,當(dāng)請求處理完成后(從Controller返回后),清除ThreadLocal中的id,避免內(nèi)存泄漏。
import com.mmall.concurrency.example.threadLocal.RequestHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class HttpInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("preHandle");
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("ThreadId:"+RequestHolder.getId());
RequestHolder.remove();
log.info("afterCompletion");
return;
}
}
最后,我們需要在spring boot啟動類上注冊我們定義的Filer及Inteceptor,并設(shè)置攔截路徑。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@SpringBootApplication
public class ConcurrencyApplication extends WebMvcConfigurerAdapter{
public static void main(String[] args) {
SpringApplication.run(ConcurrencyApplication.class, args);
}
@Bean
public FilterRegistrationBean httpFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new HttpFilter());
registrationBean.addUrlPatterns("/threadLocal/*");
return registrationBean;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HttpInterceptor()).addPathPatterns("/**");
}
}
在瀏覽器或者postman中輸入http://localhost:8080/threadLocal/test
觀察輸出結(jié)果:
2018-11-09 11:16:51.287? INFO 34076 --- [?????????? main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2018-11-09 11:16:51.290? INFO 34076 --- [?????????? main] c.m.concurrency.ConcurrencyApplication?? : Started ConcurrencyApplication in 1.718 seconds (JVM running for 2.132)
2018-11-09 11:17:03.060? INFO 34076 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/]?????? : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-11-09 11:17:03.060? INFO 34076 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet??????? : FrameworkServlet 'dispatcherServlet': initialization started
2018-11-09 11:17:03.072? INFO 34076 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet??????? : FrameworkServlet 'dispatcherServlet': initialization completed in 12 ms
2018-11-09 11:17:03.078? INFO 34076 --- [nio-8080-exec-2] com.mmall.concurrency.HttpFilter???????? : do filter, 29, /threadLocal/test
2018-11-09 11:17:03.090? INFO 34076 --- [nio-8080-exec-2] com.mmall.concurrency.HttpInterceptor??? : preHandle
2018-11-09 11:17:03.124? INFO 34076 --- [nio-8080-exec-2] com.mmall.concurrency.HttpInterceptor??? : ThreadId:29
2018-11-09 11:17:03.124? INFO 34076 --- [nio-8080-exec-2] com.mmall.concurrency.HttpInterceptor??? : afterCompletion
從打印的日志結(jié)果中,我們看到在Filter中我們將當(dāng)前線程的id 29添加到了ThreadLocal中,隨后在Inteceptor中打印并刪除了id。
ThreadLocal在springboot使用中的坑
ThreadLocal 適用于變量在線程間隔離,而在方法或類間共享的場景?,F(xiàn)在在Springboot中我做如下場景的使用:
使用 Spring Boot 創(chuàng)建一個 Web 應(yīng)用程序,使用 ThreadLocal 存放一個 Integer 的值,來暫且代表需要在線程中保存的用戶信息,這個值初始是 null。在業(yè)務(wù)邏輯中,我先從 ThreadLocal 獲取一次值,然后把外部傳入的參數(shù)設(shè)置到 ThreadLocal 中,來模擬從當(dāng)前上下文獲取到用戶信息的邏輯,隨后再獲取一次值,最后輸出兩次獲得的值和線程名稱。
@RestController
public class threadLocal {
private ThreadLocal<Integer> currentUser = ThreadLocal.withInitial(() -> null);
@RequestMapping("wrong")
public Map wrong(@RequestParam("userId") Integer userId) {
//設(shè)置用戶信息之前先查詢一次ThreadLocal中的用戶信息
String before = Thread.currentThread().getName() + ":" + currentUser.get();
//設(shè)置用戶信息到ThreadLocal
currentUser.set(userId);
//設(shè)置用戶信息之后再查詢一次ThreadLocal中的用戶信息
String after = Thread.currentThread().getName() + ":" + currentUser.get();
//匯總輸出兩次查詢結(jié)果
Map result = new HashMap();
result.put("before", before);
result.put("after", after);
return result;
}
}
為了讓問題快速的重現(xiàn),我在配置文件中設(shè)置一下 Tomcat 的參數(shù),把工作線程池最大線程數(shù)設(shè)置為 1,這樣始終是同一個線程在處理請求:
server.tomcat.max-threads=1
運行程序后先讓用戶 1 來請求接口,可以看到第一和第二次獲取到用戶 ID 分別是 null 和 1,符合預(yù)期:隨后用戶 2 來請求接口,這次就出現(xiàn)了 Bug,第一和第二次獲取到用戶 ID 分別是 1 和 2,顯然第一次獲取到了用戶 1 的信息,原因就是 Tomcat 的線程池重用了線程。

在 Tomcat 這種 Web 服務(wù)器下跑的業(yè)務(wù)代碼,本來就運行在一個多線程環(huán)境中,并不能認為沒有顯式開啟多線程就不會有線程安全問題,所以使用類似 ThreadLocal 工具來存放一些數(shù)據(jù)時,需要特別注意在代碼運行完后,顯式地去清空設(shè)置的數(shù)據(jù)。如果在代碼中使用了自定義的線程池,也同樣會遇到這個問題。修改后代碼如下:
@RestController
public class threadLocal {
private ThreadLocal<Integer> currentUser = ThreadLocal.withInitial(() -> null);
@RequestMapping("wrong")
public Map wrong(@RequestParam("userId") Integer userId) {
//設(shè)置用戶信息之前先查詢一次ThreadLocal中的用戶信息
String before = Thread.currentThread().getName() + ":" + currentUser.get();
//設(shè)置用戶信息到ThreadLocal
currentUser.set(userId);
try {
//設(shè)置用戶信息之后再查詢一次ThreadLocal中的用戶信息
String after = Thread.currentThread().getName() + ":" + currentUser.get();
//匯總輸出兩次查詢結(jié)果
Map result = new HashMap();
result.put("before", before);
result.put("after", after);
return result;
} finally {
//增加移除處理
currentUser.remove();
}
}
}
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- springboot在filter中如何用threadlocal存放用戶身份信息
- SpringBoot中的ThreadLocal保存請求用戶信息的實例demo
- springboot登錄攔截器+ThreadLocal實現(xiàn)用戶信息存儲的實例代碼
- SpringBoot ThreadLocal 簡單介紹及使用詳解
- SpringBoot+ThreadLocal+AbstractRoutingDataSource實現(xiàn)動態(tài)切換數(shù)據(jù)源
- Springboot公共字段填充及ThreadLocal模塊改進方案
- SpringBoot ThreadLocal實現(xiàn)公共字段自動填充案例講解
- SpringBoot通過ThreadLocal實現(xiàn)登錄攔截詳解流程
- SpringBoot中使用?ThreadLocal?進行多線程上下文管理及注意事項小結(jié)
相關(guān)文章
Jrebel License Server 激活 IDEA-Jrebel-在線-
這篇文章主要介紹了Jrebel License Server 激活 IDEA-Jrebel-在線-離線-均適用,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12
Springboot源碼 AbstractAdvisorAutoProxyCreator解析
這篇文章主要介紹了Springboot源碼 AbstractAdvisorAutoProxyCreator解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-08-08
SpringBoot使用AOP與注解實現(xiàn)請求參數(shù)自動填充流程詳解
面向切面編程(aspect-oriented programming,AOP)主要實現(xiàn)的目的是針對業(yè)務(wù)處理過程中的切面進行提取,諸如日志、事務(wù)管理和安全這樣的系統(tǒng)服務(wù),從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發(fā)的效率2023-02-02
SpringBoot實現(xiàn)國密SM4加密解密的使用示例
在商用密碼體系中,SM4主要用于數(shù)據(jù)加密,本文就來介紹一下SpringBoot實現(xiàn)國密SM4加密解密的使用示例,具有一定的參考價值,感興趣的可以了解一下2023-10-10

