并发编程-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

相关文章
|
2月前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
163 6
|
2月前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
2月前
|
设计模式 安全 Java
Java 多线程并发编程
Java多线程并发编程是指在Java程序中使用多个线程同时执行,以提高程序的运行效率和响应速度。通过合理管理和调度线程,可以充分利用多核处理器资源,实现高效的任务处理。本内容将介绍Java多线程的基础概念、实现方式及常见问题解决方法。
99 0
|
2月前
|
Java
线程池内部机制:线程的保活与回收策略
【10月更文挑战第24天】 线程池是现代并发编程中管理线程资源的一种高效机制。它不仅能够复用线程,减少创建和销毁线程的开销,还能有效控制并发线程的数量,提高系统资源的利用率。本文将深入探讨线程池中线程的保活和回收机制,帮助你更好地理解和使用线程池。
101 2
|
3月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
73 4
|
3月前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
42 3
|
13天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
38 1
|
3月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
63 1
|
3月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
41 3
|
3月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
28 2