线程封闭

概述

线程封闭是一种较为简单的线程并发的方法。它其实把对象封装到一个线程里,该对象只对该线程是可见的。当然也就是线程安全的了。

实现线程封闭的方法

  1. Ad-hoc 线程封闭:依赖程序控制实现,脆弱,是最糟糕的一种方式,不推荐!
  2. 堆栈封闭:应用广泛,依靠各线程局部变量的堆栈拷贝副本实现,无并发问题。避免使用全局变量。
  3. 数据库连接对应JDBC的Connection对象。
  4. ThreadLocal线程封闭:实现较好,效率较高。(以后会做源码分析……)
  1. 堆栈封闭:即指的是方法/类中的局部变量,默认是通过线程拷贝副本实现。
  2. Connection对象在实现中并未对线程安全做过多的处理,JDBC的规范中也未要求Connection对象必须是线程安全的。但实际服务器的应用程序中,线程从连接池获取Connection对象,只有在使用结束后才将其返回给连接池,期间其他线程是获取不到Connection对象的。该机制显式提供了线程封闭。

ThreadLocal测试例子

RequestHolder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RequestHolder {

private final static ThreadLocal<Long> requestHolder = new ThreadLocal<>();

public static void add(Long id) {
requestHolder.set(id);
}

public static Long getId() {
return requestHolder.get();
}

public static void remove() {
requestHolder.remove();
}
}

方法分析:

  1. 该类存放需要绑定的信息。
  2. 其中add操作是在请求进入后端服务器,但还未进行实际处理时,调用该方法,写入相关信息。(通过filter:先拦截对应的URL,当前台访问该URL时,将相关信息写入ThreadLocal中;当URL实际被处理时,可直接从ThreadLocal中取出信息)。
  3. 定义移除方法,防止内存泄漏。在接口处理完之后进行处理(通过intercepter实现)。
HttpFilter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Slf4j
public class HttpFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

//核心方法
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;

log.info("do filter, {}, {}", Thread.currentThread().getId(), request.getServletPath());

RequestHolder.add(Thread.currentThread().getId());
filterChain.doFilter(servletRequest, servletResponse);
}

//销毁
@Override
public void destroy() {

}
}

方法分析:

  1. 因为是通过http请求,ServletRequest需转换为HttpServletRequest类型。
  2. 在RequestHolder中放入URL相关信息。
  3. 最后若该filter不是想拦截住该请求,只是做相关的数据处理,还想让其他过滤器接收到,则需最后调用filterChain.doFilter(servletRequest, servletResponse)。
HttpInterceptor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Slf4j
public class HttpInterceptor extends HandlerInterceptorAdapter {

//在处理之前输出
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("preHandle");
return true;
}

//在完成之后删除信息、输出日志
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
RequestHolder.remove();
log.info("afterCompletion");
return;
}
}
ThreadLocalController.java
1
2
3
4
5
6
7
8
9
10
@Controller
@RequestMapping("/threadlocal")
public class ThreadLocalController {

@RequestMapping("/test")
@ResponseBody
public Long test() {
return RequestHolder.getId();
}
}
ConcurrencyApplication.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
true//使用springboot快速进行测试
@SpringBootApplication
public class ConcurrencyApplication extends WebMvcConfigurerAdapter {

truepublic static void main(String[] args) {
truetrueSpringApplication.run(ConcurrencyApplication.class, args);
true}

true@Bean
truepublic FilterRegistrationBean httpFilter() {
truetrueFilterRegistrationBean registrationBean = new FilterRegistrationBean();
truetrueregistrationBean.setFilter(new HttpFilter());

truetrueregistrationBean.addUrlPatterns("/threadlocal/*");
truetruereturn registrationBean;
true}

true@Override
truepublic void addInterceptors(InterceptorRegistry registry) {

truetrueregistry.addInterceptor(new HttpInterceptor()).addPathPatterns("/**");
true}
}

方法分析:

  1. 通过springboot创建registrationBean并指定过滤URL类型为”/threadlocal/*”。
  2. 重写addInterceptors -> 添加拦截器,并指定拦截的路径类型。
  3. ThreadLocalController.java中指定请求映射的名称和返回内容。

接口测试结果:(使用Postman进行接口测试)

图示

日志部分截图:

图示

可以看出例子是和日志完全对应的。
重复一下threadlocal的实现思想当一个请求进来时,通过过滤器Filter,将数据信息(这里是线程id)存储到threadlocal中,当接口被调用处理时,可以直接从中取出来;当接口处理完成,通过拦截器Interceptor的afterCompletion把当前线程中的数据信息(这里是线程id)移除,避免内存泄漏。

SupriseMF wechat
欢迎关注微信订阅号【星球码】,分享学习编程奇淫巧技~
喜欢就支持我呀(*^∇^*)~

热评文章