其实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我们就可以用另外一种方式解决:

  1. 在某个接口中定义一个静态的ThreadLocal 对象,例如 public static ThreadLocal threadLocal=new ThreadLocal ();
  2. 然后让a,b,c方法所在的类假设是类A,类B,类C都实现1中的接口
  3. 在调用a时,使用A.threadLocal.set(user) 把user对象放入ThreadLocal环境
  4. 这样我们在方法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的使用的更多相关文章

  1. java并发编程(十六)happen-before规则

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17348313 happen-before规则介绍 Java语言中有一个"先行发生 ...

  2. java并发编程(十六)----(线程池)java线程池的使用

    上节我们简单介绍了线程池,这次我们就来使用一下.Executors提供四种线程池,分别是:newCachedThreadPool,newFixedThreadPool ,newScheduledThr ...

  3. Java并发(二十):线程本地变量ThreadLocal

    ThreadLocal是一个本地线程副本变量工具类. 主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不同的 ...

  4. Java并发编程:深入剖析ThreadLocal(转载)

    Java并发编程:深入剖析ThreadLocal(转载) 原文链接:Java并发编程:深入剖析ThreadLocal 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadL ...

  5. (转)Java并发编程:深入剖析ThreadLocal

    Java并发编程:深入剖析ThreadLoca Java并发编程:深入剖析ThreadLocal 说下自己的理解:使用ThreadLocal能够实现空间换时间,重在理解ThreadLocal是如何复制 ...

  6. 【转载】 Java并发编程:深入剖析ThreadLocal

    原文链接:http://www.cnblogs.com/dolphin0520/p/3920407.html感谢作者的辛苦总结! Java并发编程:深入剖析ThreadLocal 想必很多朋友对Thr ...

  7. 7、Java并发编程:深入剖析ThreadLocal

    Java并发编程:深入剖析ThreadLocal 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用方法和实现原理.首先,本文先谈一下对ThreadLoc ...

  8. Java并发编程二三事

    Java并发编程二三事 转自我的Github 近日重新翻了一下<Java Concurrency in Practice>故以此文记之. 我觉得Java的并发可以从下面三个点去理解: * ...

  9. Java并发编程:深入剖析ThreadLocal (总结)

    ThreadLocal好处 Java并发编程的艺术解释好处是:get和set方法的调用可以不用在同一个方法或者同一个类中. 问答形式总结: 1. ThreadLocal类的作用 ThreadLocal ...

  10. java并发编程笔记(六)——AQS

    java并发编程笔记(六)--AQS 使用了Node实现FIFO(first in first out)队列,可以用于构建锁或者其他同步装置的基础框架 利用了一个int类型表示状态 使用方法是继承 子 ...

随机推荐

  1. HDU 5616:Jam's balance(背包DP)

    http://acm.hdu.edu.cn/showproblem.php?pid=5616 题意:有n个物品,每个重量为w[i],有一个天平,你可以把物品放在天平的左边或者右边,接下来m个询问,问是 ...

  2. os.path.dirname(__file__)

    os.path.dirname(__file__) 返回脚本的路径 描述: 必须实际存在的.py文件,如果直接在命令行执行,则会引发异常NameError: name 'file' is not de ...

  3. React躬行记(7)——表单

    表单元素是一类拥有内部状态的元素,这些状态由其自身维护,通过这类元素可让用户与Web应用进行交互.HTML中的表单元素(例如<input>.<select>和<radio ...

  4. 对http请求进行过滤处理,转换成接收着需要的格式

    需要在Global.asax的Application中进行初始化处理 这样:GlobalConfiguration.Configuration.MessageHandlers.Add(new Defa ...

  5. 微信小程序开发--组件(4)

    一.picker-view / picker-view-column <view> <view>{{year}}年{{month}}月{{day}}日</view> ...

  6. java学习笔记(基础篇)—数组模拟实现栈

    栈的概念 先进后出策略(LIFO) 是一种基本数据结构 栈的分类有两种:1.静态栈(数组实现) 2.动态栈(链表实现) 栈的模型图如下: 需求分析 在编写代码之前,我习惯先对要实现的程序进行需求分析, ...

  7. 洛谷P2472 [SCOI2007]蜥蜴 题解

    题目链接: https://www.luogu.org/problemnew/show/P2472 分析: 这道题用最大流解决. 首先构建模型. 一根柱子可以跳入和跳出,于是拆成两个点:入点和出点. ...

  8. 洛谷 P4363 [九省联考2018]一双木棋chess 题解

    题目链接:https://www.luogu.org/problemnew/show/P4363 分析: 首先博弈,然后考虑棋盘的规则,因为一个子在落下时它的上面和左面都已经没有空位了,所以棋子的右下 ...

  9. 了解使用wireshark抓包工具

    一.简介 1.什么是wireshark 百度: Wireshark(前称Ethereal)是一个网络封包分析软件.网络封包分析软件的功能是撷取网络封包,并尽可能显示出最为详细的网络封包资料.Wires ...

  10. 浅谈redis

    1.Redis简介: Redis是一个开源的使用ANSI C语言编写,遵守BSD协议.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.它通常被称为数据结构服务 ...