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

原文链接:https://blog.csdn.net/yangshangwei/article/details/87886079

脑图

概述

在上篇博文并发编程-10线程安全策略之不可变对象 ,我们通过介绍使用线程安全的不可变对象可以保证线程安全。

除了上述方法,还有一种办法就是:线程封闭。

线程封闭的三种方式

1、Ad-hoc 线程封闭 ,完全由程序控制实现,不可控,不要使用

2、堆栈封闭 方法中定义局部变量。不存在并发问题
(1)堆栈封闭其实就是方法中定义局部变量。不存在并发问题。

(2)多个线程访问一个方法的时候,方法中的局部变量都会被拷贝一份到线程的栈中(Java内存模型),所以局部变量是不会被多个线程所共享的。

(3)局部变量的固有属性之一就是封闭在线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。

Java虚拟机栈 请参考以前的博文 地址如下: https://blog.csdn.net/yangshangwei/article/details/52833342#java虚拟机栈-java-virtual-machine-stacks

3、ThreadLocal 线程封闭 将可变数据通过每个线程有自己的独立副本从而实现线程封闭的机制

(1)ThreadLocal类:线程本地变量。如果将变量使用ThreadLocal来包装,那么每个线程往这个ThreadLocal中读写都是线程隔离的,互相之间不会影响。

(2)它提供了一种将可变数据通过每个线程有自己的独立副本从而实现线程封闭的机制。

(3)Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap。

(4)ThreadLocalMap有自己的独立实现,可以简单地将它的key视作ThreadLocal,value为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。

(5)每个线程在往某个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变量中的value
        ArtisanUser artisanUser = USER_HOLDER.get();
        log.info("从ThreadLocal中获取artisanUser:{}",artisanUser.toString());
        return artisanUser;
        
    }
    
    public static void removeCurrentUser() {
        log.info("从ThreadLocal中移除artisanUser:{}", getCurrentUser());
        //  通过当前线程这个key获取当前线程的ThreadLocalMap,从中移除value
        USER_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:中断请求
     * 
     * 处理器执行前方法
     */
    @Override
    public 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返回之前执行
     * 
     * 处理器处理后方法
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        
    }

    /**
     * afterCompletion在整个请求执行完毕后执行,无论是否发生异常都会执行
     * 
     * 处理器完成后方法
     * 
     * 
     * 在这个方法中移除当前用户
     */
    @Override
    public 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 {
    @Override
    public 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。

观察后台日志:


end

顺便在此给大家推荐一个Java方面的交流学习群:4112676,里面会分享一些高级面试题,还有资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系,主要针对Java开发人员提升自己,突破瓶颈,相信你来学习,会有提升和收获。在这个群里会有你需要的内容 朋友们请抓紧时间加入进来吧

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容