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

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

2019080611330380.jpg

脑图



20190222222922655.png


概述


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

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


线程封闭的三种方式


2019022222281435.png


  • 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


20190222220946376.png


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中的值,否则累计太多容易造成内溢出。


20190222233907302.png


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 或者浏览器


20190223000930546.png


控制层可以直接通过RequestHold这个threalocal封装类直接获取到存放在ThreadLocal中的变量信息,说明OK。

观察后台日志:

20190223000948572.png


代码


https://github.com/yangshangwei/ConcurrencyMaster

相关文章
|
6天前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
22 4
|
13天前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
21 3
|
1月前
|
负载均衡 Java 调度
探索Python的并发编程:线程与进程的比较与应用
本文旨在深入探讨Python中的并发编程,重点比较线程与进程的异同、适用场景及实现方法。通过分析GIL对线程并发的影响,以及进程间通信的成本,我们将揭示何时选择线程或进程更为合理。同时,文章将提供实用的代码示例,帮助读者更好地理解并运用这些概念,以提升多任务处理的效率和性能。
51 3
|
18天前
|
安全 Java 数据库连接
Python多线程编程:竞争问题的解析与应对策略
Python多线程编程:竞争问题的解析与应对策略
12 0
|
18天前
|
安全 Java 数据库连接
Python多线程编程:竞争问题的解析与应对策略【2】
Python多线程编程:竞争问题的解析与应对策略【2】
12 0
|
1月前
|
并行计算 API 调度
探索Python中的并发编程:线程与进程的对比分析
【9月更文挑战第21天】本文深入探讨了Python中并发编程的核心概念,通过直观的代码示例和清晰的逻辑推理,引导读者理解线程与进程在解决并发问题时的不同应用场景。我们将从基础理论出发,逐步过渡到实际案例分析,旨在揭示Python并发模型的内在机制,并比较它们在执行效率、资源占用和适用场景方面的差异。文章不仅适合初学者构建并发编程的基础认识,同时也为有经验的开发者提供深度思考的视角。
|
1月前
|
安全 Java
LinkedBlockingQueue 是线程安全的,为什么会有两个线程都take()到同一个对象了?
LinkedBlockingQueue 是线程安全的,为什么会有两个线程都take()到同一个对象了?
34 0
|
13天前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
32 1
C++ 多线程之初识多线程
|
28天前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
13天前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
36 6