并发编程-11线程安全策略之线程封闭
文章目錄
- 腦圖
- 概述
- 線程封閉的三種方式
- 示例
- 堆棧封閉
- ThreadLocal
- Step1. ThreadLocal操作類
- Step2. 自定義過濾器
- Step3. 注冊攔截器,配置攔截規則
- Step4. Controller層調用
- Step5. 測試
- 代碼
腦圖
概述
在上篇博文并發編程-10線程安全策略之不可變對象 ,我們通過介紹使用線程安全的不可變對象可以保證線程安全。
除了上述方法,還有一種辦法就是:線程封閉。
線程封閉的三種方式
- Ad-hoc 線程封閉 ,完全由程序控制實現,不可控,不要使用
- 堆棧封閉 方法中定義局部變量。不存在并發問題
堆棧封閉其實就是方法中定義局部變量。不存在并發問題。
多個線程訪問一個方法的時候,方法中的局部變量都會被拷貝一份到線程的棧中(Java內存模型),所以局部變量是不會被多個線程所共享的。
局部變量的固有屬性之一就是封閉在線程中。它們位于執行線程的棧中,其他線程無法訪問這個棧。
Java虛擬機棧 請參考以前的博文 地址如下: https://blog.csdn.net/yangshangwei/article/details/52833342#java%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%A0%88-java-virtual-machine-stacks
- ThreadLocal 線程封閉 將可變數據通過每個線程有自己的獨立副本從而實現線程封閉的機制
ThreadLocal類:線程本地變量。如果將變量使用ThreadLocal來包裝,那么每個線程往這個ThreadLocal中讀寫都是線程隔離的,互相之間不會影響。
它提供了一種將可變數據通過每個線程有自己的獨立副本從而實現線程封閉的機制。
Thread類有一個類型為ThreadLocal.ThreadLocalMap的實例變量threadLocals,也就是說每個線程有一個自己的ThreadLocalMap。
ThreadLocalMap有自己的獨立實現,可以簡單地將它的key視作ThreadLocal,value為代碼中放入的值(實際上key并不是ThreadLocal本身,而是它的一個弱引用)。
每個線程在往某個ThreadLocal里set值的時候,都會往自己的ThreadLocalMap里存,get也是以某個ThreadLocal作為引用,在自己的map里找對應的key,從而實現了線程隔離。
示例
堆棧封閉
多個線程訪問一個方法,該方法中的局部變量都會被拷貝一份兒到線程棧中。所以局部變量是不被多個線程所共享的,也就不會出現并發問題。所以能用局部變量就別用全局的變量,全局變量容易引起并發問題。
局部變量,沒啥好說的 ,直接看ThreadLocal實現線程安全吧
ThreadLocal
假設我們將用戶信息放到ThreadLocal中,然后從ThreadLocal中獲取該用戶信息。 這個例子中的場景不是很嚴謹,僅僅僅是為了演示ThreadLocal的用法
這里我們通過攔截器(過濾器也行) ,【如果過濾器和攔截器不清楚的話,建議先看下我之前寫的博文: Spring Boot2.x-12 Spring Boot2.1.2中Filter和Interceptor 的使用 】在調用Controller之前 ,重寫攔截器的preHandle方法,通常情況下在該方法中從session中獲取user信息,將寫入到ThreadLocal, 重寫afterCompletion方法不管是方法執行正常還是異常都會執行該方法,在該方法中移除threadlocal中的值,否則累計太多容易造成內溢出。
Step1. ThreadLocal操作類
通常情況下都要具備三個方法 add get remove 。特別是remove,否則容易造成內存溢出
package com.artisan.example.threadLocal;import lombok.extern.slf4j.Slf4j;/*** 通常情況下都要具備三個方法 add get remove * 特別是remove,否則容易造成內存泄漏* @author yangshangwei**/ @Slf4j public class RequestHolder {private final static ThreadLocal<ArtisanUser> USER_HOLDER = new ThreadLocal<ArtisanUser>();public static void addCurrentUser(ArtisanUser artisanUser) {// 將當前線程作為key, artisanUser作為value 存入ThreadLocal類的ThreadLocalMap中USER_HOLDER.set(artisanUser);log.info("將artisanUser:{} 寫入到ThreadLocal",artisanUser.toString());}public static ArtisanUser getCurrentUser() {// 通過當前線程這個key ,獲取存放在當前線程的ThreadLocalMap變量中的valueArtisanUser artisanUser = USER_HOLDER.get();log.info("從ThreadLocal中獲取artisanUser:{}",artisanUser.toString());return artisanUser;}public static void removeCurrentUser() {log.info("從ThreadLocal中移除artisanUser:{}", getCurrentUser());// 通過當前線程這個key獲取當前線程的ThreadLocalMap,從中移除valueUSER_HOLDER.remove();}}Step2. 自定義過濾器
在過濾器中 ,重寫對應的方法 添加 和 移除 threadLocal
package com.artisan.interceptors;import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;import org.checkerframework.checker.index.qual.LengthOf; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;import com.artisan.example.threadLocal.ArtisanUser; import com.artisan.example.threadLocal.RequestHolder;import lombok.extern.slf4j.Slf4j;/*** 實現 Handlerlnterceptor接口,覆蓋其對應的方法即完成了攔截器的開發* * @author yangshangwei**/ @Slf4j public class MyInterceptor implements HandlerInterceptor {/*** preHandle在執行Controller之前執行 * 返回true:繼續執行處理器邏輯,包含Controller的功能 * 返回false:中斷請求* * 處理器執行前方法*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {log.info("MyInterceptor-處理器執行前方法preHandle,返回true則不攔截后續的處理");// 模擬user存在session中ArtisanUser user = new ArtisanUser();user.setName("artisan");user.setAge(20);request.getSession().setAttribute("user", user);// 將用戶信息添加到ThreadLocal中RequestHolder.addCurrentUser((ArtisanUser)request.getSession().getAttribute("user"));return true;}/*** postHandle在請求執行完之后渲染ModelAndView返回之前執行* * 處理器處理后方法*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {}/*** afterCompletion在整個請求執行完畢后執行,無論是否發生異常都會執行* * 處理器完成后方法* * * 在這個方法中移除當前用戶*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {log.info("MyInterceptor-處理器完成后方法afterCompletion");RequestHolder.removeCurrentUser();}}Step3. 注冊攔截器,配置攔截規則
注冊攔截器,配置攔截規則
package com.artisan.config;import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import com.artisan.interceptors.MyInterceptor;/*** 實現 WebMvcConfigurer 接 口, 最后覆蓋其addInterceptors方法進行注冊攔截器* @author yangshangwei**/// 標注為配置類 @Configuration public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 注冊攔截器到 Spring MVC 機制, 然后 它會返 回 一個攔截器注冊InterceptorRegistration regist = registry.addInterceptor(new MyInterceptor());// 指定攔截匹配模式,限制攔截器攔截請求regist.addPathPatterns("/artisan/threadLocal/*");}}Step4. Controller層調用
通過RequestHolder.getCurrentUser() 獲取存到ThreadLocal中的user信息
package com.artisan.controller;import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;import com.artisan.example.threadLocal.ArtisanUser; import com.artisan.example.threadLocal.RequestHolder;@RestController @RequestMapping("/artisan/threadLocal") public class ThreadLocalTestController {@GetMapping("/getCurrentUser")public ArtisanUser getCurrentUser() {return RequestHolder.getCurrentUser();} }Step5. 測試
啟動Spring Boot 工程,打開postman,請求
http://localhost:8080/artisan/threadLocal/getCurrentUser
postman 或者瀏覽器
控制層可以直接通過RequestHold這個threalocal封裝類直接獲取到存放在ThreadLocal中的變量信息,說明OK。
觀察后臺日志:
代碼
https://github.com/yangshangwei/ConcurrencyMaster
總結
以上是生活随笔為你收集整理的并发编程-11线程安全策略之线程封闭的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 并发编程-10线程安全策略之不可变对象
- 下一篇: 并发编程-12线程安全策略之常见的线程不