java并发编程(二十六)----ThreadLocal的使用
其实ThreadLocal很多接触过多线程的同学都可能会很陌生,他不像current包里面那些耳熟能详的api一样在我们面前经常出现,更多的他作为一个本地类出现在系统设计里面。我们可以说一下Spring,Spring的事务管理器通过AOP切入业务代码,在进入业务代码前,会根据对应的事务管理器提取出相应的事务对象,假如事务管理器是DataSourceTransactionManager,就会从DataSource中获取一个连接对象,通过一定的包装后将其保存在ThreadLocal中。并且Spring也将DataSource进行了包装,重写了其中的getConnection()方法,或者说该方法的返回将由Spring来控制,这样Spring就能让线程内多次获取到的Connection对象是同一个。
为什么要放在ThreadLocal里面呢?因为Spring在AOP后并不能向应用程序传递参数,应用程序的每个业务代码是事先定义好的,Spring并不会要求在业务代码的入口参数中必须编写Connection的入口参数。此时Spring选择了ThreadLocal,通过它保证连接对象始终在线程内部,任何时候都能拿到,此时Spring非常清楚什么时候回收这个连接,也就是非常清楚什么时候从ThreadLocal中删除这个元素
从名字上看我们很容易误解,ThreadLocal,本地线程。local有当地的,本地的,局部的意思,这里说的是局部线程,意思是线程的局部变量。我们知道synchronized是独占锁,同一时间只能有一个线程操作被锁住的代码大家排队等待,典型的以时间换空间的策略。那如果我们空间很足时间不够该怎么办呢,ThreadLocal就该派上用场了。ThreadLocal作为线程的局部变量,会为这个线程创建独立的变量副本,在线程的内部,他所创建的对象相当于全局对象。
说到这里,大家是不是还是没有分清楚ThreadLocal和synchronized有什么区别,下面我们来讲。
- ThreadLocal 不是用来解决共享对象的多线程访问问题的,上面说了ThreadLocal是线程的局部变量。一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
- ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。
如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。
我们来看一个例子:
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
在getSession()方法中,首先判断当前线程中有没有放进去session,如果还没有,那么通过sessionFactory().openSession()来创建一个session,再将session set到线程中,实际是放到当前线程的ThreadLocalMap这个map中,这时,对于这个session的唯一引用就是当前线程中的那个ThreadLocalMap,而threadSession作为这个值的key,要取得这个session可以通过threadSession.get()来得到,里面执行的操作实际是先取得当前线程中的ThreadLocalMap,然后将threadSession作为key将对应的值取出。上面我们也讲过每个线程进来创建threadSession 的时候,这个threadSession 只属于他一个人所有,别的线程无法共享到他自己创建的ThreadLocal。这就避免了所有线程共享同一个对象的问题。并且该session创建完成之后,我们不必走到哪里都携带着session这个参数,走到哪里传递到哪里。需要使用的时候只需要从threadlocal中取出即可。这也是极其省事的。
我们可以举一个 例子来说明ThreadLocal不是用来解决对象共享访问问题的,而是为了处理在多线程环境中,某个方法处理一个业务,需要递归依赖其他方法时,而要在这些方法中共享参数的问题。例如有方法a(),在该方法中调用了方法b(),而在b方法中又调用了方法c(),即a–>b—>c,如果a,b,c都需要使用用户对象,那么我们常用做法就是a(User user)–>b(User user)—c(User user)。但是如果使用ThreadLocal我们就可以用另外一种方式解决:
- 在某个接口中定义一个静态的ThreadLocal 对象,例如 public static ThreadLocal threadLocal=new ThreadLocal ();
- 然后让a,b,c方法所在的类假设是类A,类B,类C都实现1中的接口
- 在调用a时,使用A.threadLocal.set(user) 把user对象放入ThreadLocal环境
- 这样我们在方法a,方法b,方法c可以在不用传参数的前提下,在方法体中使用threadLocal.get()方法就可以得到user对象。
上面我们说到ThreadLocal的使用,也说了ThreadLocal里面有一个ThreadLocalMap 用于存储当前线程的对象,下面我们简单的看一下源码来理解一下这个过程。先上类图:
ThreadLocal里面有一个内部类ThreadLocalMap,在ThreadLocal内部又装了一个Entry,他继承了WeakReference,我们来看一下Entry:
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
Entry对象其实还是ThreadLocal类型的,这里我们看到ThreadLocal用了一个WeakReference包装是为了保证该ThreadLocal对象在没有被引用的时候能够及时的被gc掉。
下面再看一下ThreadLocal的get和set方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
...
...
...
}
在set方法中t.threadLocals只要不为空,便创建map对象,我们看到set方法中的key是ThreadLocal,即thread调用ThreadLocal.get()方法既可得到当前thread的threadLocal对象里面的ThreadLocalMap的值!是不是有点绕,是不是不知道为什么当前线程能调用ThreadLocal,我们看一下上面的getMap()方法,返回值是:t.threadLocals,这个t即当前线程,在Thread类里面有一个threadLocals对象,我们可以跟过去看一下,在这里限于篇幅,就只上相关的源码:
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
Thread parent = currentThread();
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
下面方法是ThreadLocal中的:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
我们在源码中看到threadLocals并未进行赋值,他一直都是一个空对象,为什么这么做呢,我们接着看下面的get方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
在get方法中,如果一个线程当前并未使用ThreadLocal对象那么getMap(t)必然是空,那我们就得想了,难道在Thread类中创建一个空对象threadLocals就这么空着?哈哈,当然不是啦,我也着急了。所以就进入了下面的setInitialValue()方法啦,这里的getMap(t)当然还是空的,那进入createMap(t, value)呗:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
终于在这里拨开云雾见月明!妈妈再也不用担心threadLocals没有人要了!上面分析的比较乱,大家就将就看,用一句话总结那就是:
在Thread类中有一个对象是threadLocals,如果在该线程运行中有ThreadLocal创建threadLocals会去找到他的!获得你在ThreadLocal中存储的值!
上面我们已经详细分析了ThreadLocal的使用和实现,那么在真实的环境中使用它有什么弊端没呢。其实使用中还真的是有很多问题的。
我们知道ThreadLocal是和当前线程绑定的,即他的生命周期是和当前线程共存,当线程结束,ThreadLocal内部的Entity对象才会被gc回收。
下面我说一个场景大家看会带来什么样的后果:如果现在是线程池对象使用了ThreadLocal来保存变量会发生什么?大家知道线程池的主要目的是为了线程复用,那么线程池中的线程基本不会结束,与jvm的生命周期是一致的。那这个时候谁知道一个携带了ThreadLocal的线程会什么时候结束呢。长久以往必然造成内存泄露。
另外我们再说一个关于忘记释放的问题。如果你在线程刚开始进来的时候就载入了ThreadLocal用来保存变量,假设你的程序设计的不是很健壮,你忘记了写remove()。这个时候事情就来了。再假设你在ThreadLocal中存放了map对象,真实的业务中Map对象也许包含了很多数据,随着时间流逝,内存中的无用对象越来越多,内存泄露是必然的。
关于ThreadLocal的内容我们就讲到这里,其实里面还有很多值得我们深究的东西,慢慢一点点的去看吧!
java并发编程(二十六)----ThreadLocal的使用的更多相关文章
- java并发编程(十六)happen-before规则
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17348313 happen-before规则介绍 Java语言中有一个"先行发生 ...
- java并发编程(十六)----(线程池)java线程池的使用
上节我们简单介绍了线程池,这次我们就来使用一下.Executors提供四种线程池,分别是:newCachedThreadPool,newFixedThreadPool ,newScheduledThr ...
- Java并发(二十):线程本地变量ThreadLocal
ThreadLocal是一个本地线程副本变量工具类. 主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不同的 ...
- Java并发编程:深入剖析ThreadLocal(转载)
Java并发编程:深入剖析ThreadLocal(转载) 原文链接:Java并发编程:深入剖析ThreadLocal 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadL ...
- (转)Java并发编程:深入剖析ThreadLocal
Java并发编程:深入剖析ThreadLoca Java并发编程:深入剖析ThreadLocal 说下自己的理解:使用ThreadLocal能够实现空间换时间,重在理解ThreadLocal是如何复制 ...
- 【转载】 Java并发编程:深入剖析ThreadLocal
原文链接:http://www.cnblogs.com/dolphin0520/p/3920407.html感谢作者的辛苦总结! Java并发编程:深入剖析ThreadLocal 想必很多朋友对Thr ...
- 7、Java并发编程:深入剖析ThreadLocal
Java并发编程:深入剖析ThreadLocal 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用方法和实现原理.首先,本文先谈一下对ThreadLoc ...
- Java并发编程二三事
Java并发编程二三事 转自我的Github 近日重新翻了一下<Java Concurrency in Practice>故以此文记之. 我觉得Java的并发可以从下面三个点去理解: * ...
- Java并发编程:深入剖析ThreadLocal (总结)
ThreadLocal好处 Java并发编程的艺术解释好处是:get和set方法的调用可以不用在同一个方法或者同一个类中. 问答形式总结: 1. ThreadLocal类的作用 ThreadLocal ...
- java并发编程笔记(六)——AQS
java并发编程笔记(六)--AQS 使用了Node实现FIFO(first in first out)队列,可以用于构建锁或者其他同步装置的基础框架 利用了一个int类型表示状态 使用方法是继承 子 ...
随机推荐
- 【测试-测试心得】测试发现BUG后,怎么定位问题?
一.判断前后端 Safari中可以右键,选择“检查元素”,再选择“网络”同样可以抓取到http请求,如图 怎么区分是前端错误还是后端错误? 1.在开发者工具中,如果console中有报错,则表示前端代 ...
- Django中使用JS通过DataTable实现表格前端分页,每页显示页数,搜索等功能
Django架构中自带了后端分页的技术,通过Paginator进行分页,前端点击按钮提交后台进行页面切换. 优缺点:后端分页对于数据量大的场景有其优势,但页面切换比较慢. 后端分页python3代码如 ...
- 1.Solr安装与配置
1.Solr安装 1:安装 Tomcat,解压缩即可. 2:解压 solr. 3:把 solr 下的dist目录solr-4.10.3.war部署到 Tomcat\webapps下(去掉版本号). 4 ...
- 【基础算法-模拟-例题-金币】-C++
原题链接:P2669 金币 这道题目完全是一道模拟题,只要按照题目中的加金币的算法和sum累加就可以很轻易得出最终答案. 说一下有一些点需要注意: 1.用i来计每天发的金币数,n来计已经拿了金币的天数 ...
- [LeetCode] 32. Longest Valid Parentheses (hard)
原题链接 题意: 寻找配对的(),并且返回最长可成功配对长度. 思路 配对的()必须是连续的,比如()((),最长长度为2:()(),最长长度为4. 解法一 dp: 利用dp记录以s[i]为终点时,最 ...
- C语言编程入门之--第一章初识程序
第一章 初识程序 导读:计算机程序无时不刻的影响着人类的生活,现代社会已经离不开程序,程序的作用如此巨大,那么程序到底是什么呢?本章主要讨论程序的概念,唤起读者对程序的兴趣,同时对C语言程序与其它语言 ...
- u盘制作启动盘步骤以及安装win10步骤
1.下载制作工具:微PE工具箱V2.0 http://www.wepe.com.cn/download.html 2.默认制作启动盘 3.下载win10镜像 ed2k://|file|cn_windo ...
- mount命令中offset参数的意义
mount命令中offset参数的意义 感觉好久没有来写东西了,最近一直忙个不停,今天也一样,总感觉时间不够用,唉,这里来临时总结一下工作中的一点小收获吧.今天要说的是我们常用的解压IM ...
- linux初学者-iscsi篇
linux初学者-iscsi篇 之前介绍过网络文件共享系统NFS和CIFS.在系统中,设备也是可以共享的,这就是iSCSI,它可以用来建立和管理IP存储设备.主机和客户机等之间的相互 ...
- 配置 IDEA 远程连接应用服务器
当调试 Web 应用时,经常需要使用 ide 远程连接,来进行 debug 调试.使用 Springboot 内置服务器和使用 Tomcat 服务器是常见的应用部署方式,可以用不同的配置方式来启动远程 ...