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类型表示状态 使用方法是继承 子 ...
随机推荐
- C#中的委托和事件(下篇)
上次以鸿门宴的例子写了一篇博文,旨在帮助C#初学者迈过委托和事件这道坎,能够用最快的速度掌握如何使用它们.如果觉得意犹未尽,或者仍然不知如何在实际应用中使用它们,那么,这篇窗体篇,将在Winform场 ...
- wcf服务编程(一)
步骤一:定义契约 [ServiceContract] //定义服务契约 需要引用System.ServiceModel public interface ICalculator { [Operatio ...
- 9.18考试 第二题Dinner题解
当时初步感觉是一个类似动归或者贪心的神题,然而由于本题已经给出顺序,贪心貌似并没有什么道理,所以放弃贪心.然后又由于这是一个环的问题,我想到了“合并石子”那种环转链的思路,然后就是一个O(n^2*m) ...
- ~~核心编程(二):面向对象——类&属性~~
进击のpython 类&属性 虽然我们上一part写了一个面向对象的程序:人狗大战 但是如果在面向对象来看 你这些的就不够规范 你既然选择用面向对象的思想来写 那你就要符合人家的定义规范和操作 ...
- android surfaView surfaHolder video 播放
主文件 package cn.com.sxp;import android.app.Activity;import android.media.AudioManager;import android. ...
- 如何在vue中使用echart
1.安装echarts依赖 npm install echarts --save 2.在main.js中全局中引用 import echarts from 'echarts' Vue.protot ...
- Android解决RecyclerView中的item显示不全方案
最近的项目中实现订单确定页面.需要使用ScrollView嵌套RecyclerView,当RecyclerView中的item数量比较多时,就会出现item只显示一部分数据,并没有将用户勾选的商品数量 ...
- iOS-监听原生H5性能数据window.performance
WebKit-WKWebView iOS8开始苹果推荐使用WKWebview作为H5开发的核心组件,以替代原有的UIWebView,以下是webkit基本介绍介绍: 介绍博客 Webkit H5 - ...
- join,列表和字典用for循环的删除,集合,深浅拷贝
1.join() 将列表转换成字符串,并且每个字符之间用另一个字符连接起来,join后面必须是可迭代的对象(字符串,列表,元组,字典,集合),数字不能迭代 例如: s = ['a','b','c'] ...
- iOS程序员如何提升核心竞争力,防止自己被裁员?
前言: 核心竞争力最早由普拉哈拉德和加里·哈默尔两位教授提出,通常认为核心竞争力,即企业或个人相较于竞争对手而言所具备的竞争优势与核心能力差异,说白了就是你的优势,而且最好是独一无二的的优势,这就是核 ...