日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

并发编程-11线程安全策略之线程封闭

發(fā)布時間:2025/3/21 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 并发编程-11线程安全策略之线程封闭 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

  • 腦圖
  • 概述
  • 線程封閉的三種方式
  • 示例
    • 堆棧封閉
    • ThreadLocal
    • Step1. ThreadLocal操作類
    • Step2. 自定義過濾器
    • Step3. 注冊攔截器,配置攔截規(guī)則
    • Step4. Controller層調(diào)用
    • Step5. 測試
  • 代碼

腦圖


概述

在上篇博文并發(fā)編程-10線程安全策略之不可變對象 ,我們通過介紹使用線程安全的不可變對象可以保證線程安全。

除了上述方法,還有一種辦法就是:線程封閉。


線程封閉的三種方式

  • Ad-hoc 線程封閉 ,完全由程序控制實(shí)現(xiàn),不可控,不要使用

  • 堆棧封閉 方法中定義局部變量。不存在并發(fā)問題

堆棧封閉其實(shí)就是方法中定義局部變量。不存在并發(fā)問題。

多個線程訪問一個方法的時候,方法中的局部變量都會被拷貝一份到線程的棧中(Java內(nèi)存模型),所以局部變量是不會被多個線程所共享的。

局部變量的固有屬性之一就是封閉在線程中。它們位于執(zhí)行線程的棧中,其他線程無法訪問這個棧。

Java虛擬機(jī)棧 請參考以前的博文 地址如下: 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 線程封閉 將可變數(shù)據(jù)通過每個線程有自己的獨(dú)立副本從而實(shí)現(xiàn)線程封閉的機(jī)制

ThreadLocal類:線程本地變量。如果將變量使用ThreadLocal來包裝,那么每個線程往這個ThreadLocal中讀寫都是線程隔離的,互相之間不會影響。

它提供了一種將可變數(shù)據(jù)通過每個線程有自己的獨(dú)立副本從而實(shí)現(xiàn)線程封閉的機(jī)制。

Thread類有一個類型為ThreadLocal.ThreadLocalMap的實(shí)例變量threadLocals,也就是說每個線程有一個自己的ThreadLocalMap。

ThreadLocalMap有自己的獨(dú)立實(shí)現(xiàn),可以簡單地將它的key視作ThreadLocal,value為代碼中放入的值(實(shí)際上key并不是ThreadLocal本身,而是它的一個弱引用)。

每個線程在往某個ThreadLocal里set值的時候,都會往自己的ThreadLocalMap里存,get也是以某個ThreadLocal作為引用,在自己的map里找對應(yīng)的key,從而實(shí)現(xiàn)了線程隔離。


示例

堆棧封閉

多個線程訪問一個方法,該方法中的局部變量都會被拷貝一份兒到線程棧中。所以局部變量是不被多個線程所共享的,也就不會出現(xiàn)并發(fā)問題。所以能用局部變量就別用全局的變量,全局變量容易引起并發(fā)問題。

局部變量,沒啥好說的 ,直接看ThreadLocal實(shí)現(xiàn)線程安全吧


ThreadLocal

假設(shè)我們將用戶信息放到ThreadLocal中,然后從ThreadLocal中獲取該用戶信息。 這個例子中的場景不是很嚴(yán)謹(jǐn),僅僅僅是為了演示ThreadLocal的用法

這里我們通過攔截器(過濾器也行) ,【如果過濾器和攔截器不清楚的話,建議先看下我之前寫的博文: Spring Boot2.x-12 Spring Boot2.1.2中Filter和Interceptor 的使用 】在調(diào)用Controller之前 ,重寫攔截器的preHandle方法,通常情況下在該方法中從session中獲取user信息,將寫入到ThreadLocal, 重寫afterCompletion方法不管是方法執(zhí)行正常還是異常都會執(zhí)行該方法,在該方法中移除threadlocal中的值,否則累計太多容易造成內(nèi)溢出。

Step1. ThreadLocal操作類

通常情況下都要具備三個方法 add get remove 。特別是remove,否則容易造成內(nèi)存溢出

package com.artisan.example.threadLocal;import lombok.extern.slf4j.Slf4j;/*** 通常情況下都要具備三個方法 add get remove * 特別是remove,否則容易造成內(nèi)存泄漏* @author yangshangwei**/ @Slf4j public class RequestHolder {private final static ThreadLocal<ArtisanUser> USER_HOLDER = new ThreadLocal<ArtisanUser>();public static void addCurrentUser(ArtisanUser artisanUser) {// 將當(dāng)前線程作為key, artisanUser作為value 存入ThreadLocal類的ThreadLocalMap中USER_HOLDER.set(artisanUser);log.info("將artisanUser:{} 寫入到ThreadLocal",artisanUser.toString());}public static ArtisanUser getCurrentUser() {// 通過當(dāng)前線程這個key ,獲取存放在當(dāng)前線程的ThreadLocalMap變量中的valueArtisanUser artisanUser = USER_HOLDER.get();log.info("從ThreadLocal中獲取artisanUser:{}",artisanUser.toString());return artisanUser;}public static void removeCurrentUser() {log.info("從ThreadLocal中移除artisanUser:{}", getCurrentUser());// 通過當(dāng)前線程這個key獲取當(dāng)前線程的ThreadLocalMap,從中移除valueUSER_HOLDER.remove();}}

Step2. 自定義過濾器

在過濾器中 ,重寫對應(yīng)的方法 添加 和 移除 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;/*** 實(shí)現(xiàn) Handlerlnterceptor接口,覆蓋其對應(yīng)的方法即完成了攔截器的開發(fā)* * @author yangshangwei**/ @Slf4j public class MyInterceptor implements HandlerInterceptor {/*** preHandle在執(zhí)行Controller之前執(zhí)行 * 返回true:繼續(xù)執(zhí)行處理器邏輯,包含Controller的功能 * 返回false:中斷請求* * 處理器執(zhí)行前方法*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {log.info("MyInterceptor-處理器執(zhí)行前方法preHandle,返回true則不攔截后續(xù)的處理");// 模擬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在請求執(zhí)行完之后渲染ModelAndView返回之前執(zhí)行* * 處理器處理后方法*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {}/*** afterCompletion在整個請求執(zhí)行完畢后執(zhí)行,無論是否發(fā)生異常都會執(zhí)行* * 處理器完成后方法* * * 在這個方法中移除當(dāng)前用戶*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {log.info("MyInterceptor-處理器完成后方法afterCompletion");RequestHolder.removeCurrentUser();}}

Step3. 注冊攔截器,配置攔截規(guī)則

注冊攔截器,配置攔截規(guī)則

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;/*** 實(shí)現(xiàn) WebMvcConfigurer 接 口, 最后覆蓋其addInterceptors方法進(jìn)行注冊攔截器* @author yangshangwei**/// 標(biāo)注為配置類 @Configuration public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 注冊攔截器到 Spring MVC 機(jī)制, 然后 它會返 回 一個攔截器注冊InterceptorRegistration regist = registry.addInterceptor(new MyInterceptor());// 指定攔截匹配模式,限制攔截器攔截請求regist.addPathPatterns("/artisan/threadLocal/*");}}

Step4. Controller層調(diào)用

通過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

總結(jié)

以上是生活随笔為你收集整理的并发编程-11线程安全策略之线程封闭的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。