java高并发(九)线程封闭
上一節介紹了不可變對象,通過在某些情況下將不會修改的類對象設計成不可變對象,來讓對象在多個線程間保證對象是線程安全的。歸根到底,相當于躲避開了并發問題,實現好的并發是一件很困難的事情,所以很多時候都想躲避并發,避免并發除了設計成不可變對象其實還有一個簡單的方法就是線程封閉。
什么是線程封閉?
其實就是把對象封裝到一個線程里,只有這一個線程能看到這個對象。那么這個對象就算不是線程安全的,也不會出現線程安全方面的問題了,因為他只能在一個線程里面進行訪問。那么如何實現線程封閉呢?
如何實現線程封閉?
- Ad-hoc 線程封閉:程序控制實現,最糟糕,忽略
- 堆棧封閉:局部變量,無并發問題。是我們現實中使用最多的封閉了,簡單說就是局部變量。多個線程訪問一個方法的時候,方法中的局部變量都會被拷貝一份到線程棧中,所以局部變量是不會被線程共享的,因此不會出現并發問題。所以全局變量容易引起并發問題。
- ThreadLocal線程封閉:特別好的封閉方法。ThreadLocal內部維護了一個map,key是線程的名稱,value就是封閉的對象。
?ThreadLocal
正常來講我們每一個請求對服務器來講都是一個線程在運行,我們希望線程間隔離,一個線程在被后端服務器實際處理的時候,可以通過Filter過濾器取出當前的用戶,然后將數據存放在ThreadLocal中,當線程被接口的service以及其他相關類進行處理的時候很可能需要在取出當前用戶,這時就可以隨時隨地從ThreadLocal中直接拿到之前存儲的值這樣用起來就很方便了。如果我們不這樣做,會有什么麻煩呢?因為我們的登錄用戶通常是從request中取出來的,因此需要帶上request或者從request中取出來的用戶信息,從controller層開始不停的往下傳,甚至會傳到一些util類中,這樣會使得代碼看起來很臃腫。當使用ThreadLocal和Filter,就可以很方便的在接口處理之前,前取出相關的信息,在接口實際處理的時候,什么時候需要什么時候再把信息取出來,這樣代碼在設計的時候就容易多了,不至于把request從controller一直傳遞下去。
具體使用實例如下:
新建一個類:
public class RequestHolder {//因為當前沒有登錄用戶,我們用線程id來充當private final static ThreadLocal<Long> requestHolder = new ThreadLocal<>();/*** 請求進入到后端服務器,但是還沒有實際處理的時候調用add,可以使用Filter* @param id*/public static void add(Long id) {//雖然只傳入id,但是threadLocal會取出當前線程id放到map中的key,value是傳入的值requestHolder.set(id);}public static Long getId() {return requestHolder.get();}/*** 如果不做remove的話,會造成內存泄漏,數據永遠不會釋放掉* 需要在接口真正處理完成之后進行調用,可以使用interceptor*/public static void remove() {requestHolder.remove();} }這個類就用來存放ThreadLocal。
新建一個Filter:
@Slf4j public class HttpFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest httpServletRequest = (HttpServletRequest) request; // httpServletRequest.getSession().getAttribute("user");log.info("do filter , {}, {}", Thread.currentThread().getId(), ((HttpServletRequest) request).getServletPath());RequestHolder.add(Thread.currentThread().getId());// 如果這個Filter不想攔截住這個請求,只想做單獨的數據處理時,要調用chain.doFilter,使得攔截器處理完chain.doFilter(request, response);}@Overridepublic void destroy() {} }新建一個Interceptor:
@Slf4j public class HttpInterceptor extends HandlerInterceptorAdapter {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("preHandle");return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {RequestHolder.remove();log.info("afterCompletion");return ;} }配置filter和interceptor:
@Configuration public class Config implements WebMvcConfigurer {@Beanpublic FilterRegistrationBean<HttpFilter> httpFilter(){FilterRegistrationBean<HttpFilter> filterRegistrationBean = new FilterRegistrationBean<>();// 設置filterfilterRegistrationBean.setFilter(new HttpFilter());// 攔截規則filterRegistrationBean.addUrlPatterns("/threadLocal/*");return filterRegistrationBean;}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new HttpInterceptor()).addPathPatterns("/**");} }新建一個controller進行測試:
@RestController @RequestMapping("threadLocal") public class ThreadLocalController {@GetMapping("/test")public Long test() {return RequestHolder.getId();}}在瀏覽器中輸入localhost:8080/threadLocal/test可以輸出線程的id,與后臺的輸出id一致。
總結
以上是生活随笔為你收集整理的java高并发(九)线程封闭的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java高并发(八)不可变对象
- 下一篇: java高并发(十)线程不安全类与写法