其实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. PLC_SIM 出现I/O访问错误-技术论坛-工业支持中心-西门子中国

    PLC_SIM 作为SIEMENS S7-300/400 系列PLC 的仿真软件,在使用时需要有些注意事项,毕竟任何的仿真软件和真正的设备还是有一定差异的,由此而产生的误会经常会令很多客户摸不着头脑, ...

  2. ServiceFabric极简文档-5.0 Service Fabric有状态与无状态

    Service Fabric 应用程序方案 2017/08/14 作者 Edward Chen Jack Zeng Azure Service Fabric提供了一个可靠而灵活的平台,可用于编写和运行 ...

  3. Spring Boot微服务电商项目开发实战 --- 多环境部署配置、端口号统一配置及Dubbo提供者消费者实现

    昨天已经搭建好了SpringBoot基于Maven的基础父子级项目,今天开始进入项目分模块及分布式实现.首先我们基于昨天的项目,在父级工程下建lyn-sys,lyn-customer,lyn-good ...

  4. requests.exceptions.ChunkedEncodingError: ('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))【已解决】

     问题: 跑python自动化时出现报错如下图 解决: requests请求时,后面加上参数:stream=True. 参考外国小哥:https://stackoverflow.com/questio ...

  5. ElasticSearch01--安装ElasticSearch服务(Linux)

    在linux系统上安装ElasticSearch服务 Linux系统要求: 1.centos6或centos7 2.jdk1.8及以上 1. 新建用户 新建一个用户 useradd yuank 修改用 ...

  6. java ServletContextListener 实现UDP监听

    使用spring boot实现项目启动时的监听, UDPListener import java.io.IOException;import java.io.UnsupportedEncodingEx ...

  7. java中this 和 super关键字的作用

    emmmmmm也真的是好久没有写过java了,因为项目需要, 最近又必须重新拾起来了,虽然好多东西也都忘得差不多了.... 然后发现 竟然把super和this傻傻分不清.... 开个帖子记录一下: ...

  8. Scrapy爬虫框架与常用命令

    07.08自我总结 一.Scrapy爬虫框架 大体框架 2个桥梁 二.常用命令 全局命令 startproject 语法:scrapy startproject <project_name> ...

  9. 【区分】Typescript 中 interface 和 type

    在接触 ts 相关代码的过程中,总能看到 interface 和 type 的身影.只记得,曾经遇到 type 时不懂查阅过,记得他们很像,相同的功能用哪一个都可以实现.但最近总看到他们,就想深入的了 ...

  10. Linux系统安装jdk——rpm版

    这里简单地阐述一下rpm.deb.tar.gz的区别. rpm格式的软件包适用于基于Red Hat发行版的系统,如Red Hat Linux.SUSE.Fedora. deb格式的软件包则是适用于基于 ...