本博客系列是学习并发编程过程中的记录总结。由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅。

并发编程系列博客传送门


什么是ThreadLocal

ThreadLocal有点类似于Map类型的数据变量。ThreadLocal类型的变量每个线程都有自己的一个副本,某个线程对这个变量的修改不会影响其他线程副本的值,可以说ThreadLocal为我们提供了一个保证线程安全的新思路。需要注意的是一个ThreadLocal变量,其中只能set一个值。

ThreadLocal<String> localName = new ThreadLocal();
localName.set("name1");
String name = localName.get();

在线程1中初始化了一个ThreadLocal对象localName,并通过set方法,保存了一个值,同时在线程1中通过 localName.get()可以拿到之前设置的值,但是如果在线程2中,拿到的将是一个null。

下面来看下ThreadLocal的源码:

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
} public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

可以发现,每个线程中都有一个 ThreadLocalMap数据结构,当执行set方法时,其值是保存在当前线程的 threadLocals变量中,当执行get方法中,是从当前线程的 threadLocals变量获取。 (ThreadLocalMap的key值是ThreadLocal类型)

所以在线程1中set的值,对线程2来说是摸不到的,而且在线程2中重新set的话,也不会影响到线程1中的值,保证了线程之间不会相互干扰。

上面提到ThreadLoal的变量都是存储在ThreadLoalMap的变量中,下面给出下Thread、ThreadLoal和ThreadLoalMap的关系。

Thread类有属性变量threadLocals (类型是ThreadLocal.ThreadLocalMap),也就是说每个线程有一个自己的ThreadLocalMap ,所以每个线程往这个ThreadLocal中读写隔离的,并且是互相不会影响的。一个ThreadLocal只能存储一个Object对象,如果需要存储多个Object对象那么就需要多个ThreadLocal!

ThreadLocal使用场景

说完ThreadLocal的原理,我们来看看ThreadLocal的使用场景。

1. 保存线程上下文信息,在任意需要的地方可以获取

比如我们在使用Spring MVC时,想要在Service层使用HttpServletRequest。一种方式就是在Controller层将这个变量传给Service层,但是这种写法不够优雅。Spring早就帮我们想到了这种情况,而且提供了现成的工具类:

public static final HttpServletRequest getRequest(){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
return request;
} public static final HttpServletResponse getResponse(){
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
return response;
}

上面的代码就是使用ThreadLocal实现变量在线程各处传递的。

2. 保证某些情况下的线程安全,提升性能

性能监控,如记录一下请求的处理时间,得到一些慢请求(如处理时间超过500毫秒),从而进行性能改进。这边我们以Spring MVC的拦截器功能为列子。


public class StopWatchHandlerInterceptor extends HandlerInterceptorAdapter {
//NamedThreadLocal是Spring对ThreadLocal的封装,原理一样
//在多线程情况下,startTimeThreadLocal变量必须每个线程之间隔离
private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {
//1、开始时间
long beginTime = System.currentTimeMillis();
//线程绑定变量(该数据只有当前请求的线程可见)
startTimeThreadLocal.set(beginTime);
//继续流程
return true;
} @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception {
long endTime = System.currentTimeMillis();//2、结束时间
long beginTime = startTimeThreadLocal.get();//得到线程绑定的局部变量(开始时间)
long consumeTime = endTime - beginTime;//3、消耗的时间
if(consumeTime > 500) {//此处认为处理时间超过500毫秒的请求为慢请求
//TODO 记录到日志文件
System.out.println(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
}
}
}

说明:其实要实现上面的功能,完全可以不用ThreadLocal(同步锁等),但是上面的代码的确是说明ThreadLocal这个是用场景很好的列子。

ThreadLocal的最佳实践

从上面的图中可以看到,Entry的key指向ThreadLocal用虚线表示弱引用 ,下面我们来看看ThreadLocalMap:

java对象的引用包括 : 强引用,软引用,弱引用,虚引用 。

弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,该对象仅仅被弱引用关联,那么就会被回收。当仅仅只有ThreadLocalMap中的Entry的key指向ThreadLocal的时候,ThreadLocal会进行回收的!!!

ThreadLocal被垃圾回收后,在ThreadLocalMap里对应的Entry的键值会变成null,但是Entry是强引用,那么Entry里面存储的Object,并没有办法进行回收,所以ThreadLocalMap 存在内存泄露的风险。

所以最佳实践,应该在我们不使用的时候,主动调用remove方法进行清理。这里给出一个建议方案:


public class Dynamicxx { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public void dosomething(){
try {
contextHolder.set("name");
// 其它业务逻辑
} finally {
contextHolder .remove();
}
} }

简单总结

  • 每个Thread对象内部都有一个ThreadLoacalMap的成员变量,这个变量类似一个Map类型,其中key为我们定义的ThreadLocal变量的this引用,value则为我们使用set方法设置的值;
  • 如果线程不消亡,在ThreadLocalMap中存放的ThreadLocal实例对象可能一直不会清除,所以当我们不需要在使用ThreadLocal的值时,就应该手动调用remove方法清除该值。

参考

【并发编程】ThreadLocal其实很简单的更多相关文章

  1. 9.并发编程--ThreadLocal

    并发编程--ThreadLocal 1. ThreadLocal : * 线程局部变量,是一种多个线程间并发访问变量的解决方案. * 与其使用synchronized等加锁的方式,ThreadLoca ...

  2. shell编程其实真的很简单(四)

    上篇我们学习了shell中条件选择语句的用法.接下来本篇就来学习循环语句.在shell中,循环是通过for, while, until命令来实现的.下面就分别来看看吧. for for循环有两种形式: ...

  3. 【并发编程】一个最简单的Java程序有多少线程?

    一个最简单的Java程序有多少线程? 通过下面程序可以计算出当前程序的线程总数. import java.lang.management.ManagementFactory; import java. ...

  4. shell编程其实真的很简单(一)

    如今,不会Linux的程序员都不意思说自己是程序员,而不会shell编程就不能说自己会Linux.说起来似乎shell编程很屌啊,然而不用担心,其实shell编程真的很简单. 背景 什么是shell编 ...

  5. shell编程其实真的很简单(二)

    上篇我们学会了如何使用及定义变量.按照尿性,一般接下来就该学基本数据类型的运算了. 没错,本篇就仍是这么俗套的来讲讲这无聊但又必学的基本数据类型的运算了. 基本数据类型运算 操作符 符号 语义 描述 ...

  6. shell编程其实真的很简单(三)

    通过前两篇文章,我们掌握了shell的一些基本写法和变量的使用,以及基本数据类型的运算.那么,本次就将要学习shell的结构化命令了,也就是我们其它编程语言中的条件选择语句及循环语句. 不过,在学习s ...

  7. shell编程其实真的很简单(五)

    通过前几篇文章的学习,我们学会了shell的基本语法.在linux的实际操作中,我们经常看到命令会有很多参数,例如:ls -al 等等,那么这个参数是怎么处理的呢? 接下来我们就来看看shell脚本对 ...

  8. Java并发编程之闭锁CountDownLatch简单介绍

    闭锁相当于一扇门,在闭锁到达结束状态之前,这扇门一直是关闭着的,没有不论什么线程能够通过,当到达结束状态时.这扇门才会打开并容许全部线程通过.它能够使一个或多个线程等待一组事件发生. 闭锁状态包含一个 ...

  9. 【憩园】C#并发编程之概述

    写在前面 并发编程一直都存在,只不过过去的很长时间里,比较难以实现,随着互联网的发展,人口红利的释放,更加友好的支持并发编程已经成了主流编程语言的标配,而对于软件开发人员来说,没有玩过并发编程都会有点 ...

随机推荐

  1. Spring MVC拦截器学习

    1 介绍 Spring Web MVC是基于Servlet API构建的原始Web框架. 2 拦截器 2.1 定义 springmvc框架的一种拦截机制 2.2 使用 2.2.1 两步走 实现Hand ...

  2. mysql中间件分享(Mysql-prxoy,Atlas,DBProxy,Amoeba,cobar,TDDL)

    hello 各位小伙伴大家好,我是小栈君,这期我们分享关于mysql中间件的研究,也就是数据层的读写分离和负载均衡,希望能够在实际的应用中能够帮助到各位小伙伴. 下期我们将继续分享go语言的系列讲解, ...

  3. A-06 最小角回归法

    目录 最小角回归法 一.举例 二.最小角回归法优缺点 2.1 优点 2.2 缺点 三.小结 更新.更全的<机器学习>的更新网站,更有python.go.数据结构与算法.爬虫.人工智能教学等 ...

  4. 古剑奇谭三已取消该页导航,B站版本无法登陆

    最近登陆古剑三突然出现这个问题 怎么重开也无法登陆 最后发现是Ie设置问题 解决方法 点中间这个圆形图标打开 小娜 搜索“ie” 点击打开ie之后点右上角的小齿轮 选择“internet选项” 连接- ...

  5. 【Python3爬虫】我爬取了七万条弹幕,看看RNG和SKT打得怎么样

    一.写在前面 直播行业已经火热几年了,几个大平台也有了各自独特的“弹幕文化”,不过现在很多平台直播比赛时的弹幕都基本没法看的,主要是因为网络上的喷子还是挺多的,尤其是在观看比赛的时候,很多弹幕不是喷选 ...

  6. Redis实现分布式文件夹锁

    缘起 最近做一个项目,类似某度云盘,另外附加定制功能,本人负责云盘相关功能实现,这个项目跟云盘不同的是,以项目为分配权限的单位,同一个项目及子目录所有有权限的用户可以同时操作所有文件,这样就很容易出现 ...

  7. django html母版

    08.12自我总结 django母版 一.母版写的格式 在需要导入的地方写 {% block 名字定义 %} {% endblock %} 二.导入模板 {% extends 'FUCK.html' ...

  8. vc++中字符串的免杀

    一:格式字符: http://baike.baidu.com/view/2194593.htm d:以十进制形式输出带符号整数(正数不输出符号)o:以八进制形式输出无符号整数(不输出前缀o)x:以十六 ...

  9. PHP array_splice

    1.函数的作用:数组中元素的删除和替代 2.函数的参数: @params array  &$array @params int      $offset @params int      $l ...

  10. CDN实现原理

    避让:尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快.更稳定. 检测:通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时监 ...