##ThreadLocal概述## 学习JDK中的类,首先看下JDK API对此类的描述,描述以下: 该类提供了线程局部 (thread-local) 变量。这些变量不一样于它们的普通对应物,由于访问某个变量(经过其 get 或 set 方法)的每一个线程都有本身的局部变量,它独立于变量的初始化副本。ThreadLocal其实就是一个工具类,用来操做线程局部变量,ThreadLocal 实例一般是类中的 private static 字段。它们但愿将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。 例如,如下类生成对每一个线程惟一的局部标识符。线程 ID 是在第一次调用UniqueThreadIdGenerator.getCurrentThreadId()
时分配的,在后续调用中不会更改。java
其实ThreadLocal并不是是一个线程的本地实现版本,它并非一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用很是简单,就是为每个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每个线程均可以独立地改变本身的副本,而不会和其它线程的副本冲突。python
import java.util.concurrent.atomic.AtomicInteger; public class UniqueThreadIdGenerator { private static final AtomicInteger uniqueId = new AtomicInteger(0); private static final ThreadLocal < Integer > uniqueNum = new ThreadLocal < Integer > () { @Override protected Integer initialValue() { return uniqueId.getAndIncrement(); } }; public static int getCurrentThreadId() { return uniqueId.get(); } }
从线程的角度看,每一个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的而且 ThreadLocal 实例是可访问的;在线程消失以后,其线程局部实例的全部副本都会被垃圾回收(除非存在对这些副本的其余引用)。web
API表达了下面几种观点:算法
既然定义为类变量,为什么为每一个线程维护一个副本(姑且成为‘拷贝’容易理解),让每一个线程独立访问?多线程编程的经验告诉咱们,对于线程共享资源(你能够理解为属性),资源是否被全部线程共享,也就是说这个资源被一个线程修改是否影响另外一个线程的运行,若是影响咱们须要使用synchronized同步,让线程顺序访问。数据库
ThreadLocal适用于资源共享但不须要维护状态的状况,也就是一个线程对资源的修改,不影响另外一个线程的运行;这种设计是空间换时间,synchronized顺序执行是时间换取空间。apache
##ThreadLocal介绍##编程
从字面上来理解ThreadLocal,感受就是至关于线程本地的。咱们都知道,每一个线程在jvm的虚拟机里都分配有本身独立的空间,线程之间对于本地的空间是相互隔离的。那么ThreadLocal就应该是该线程空间里本地能够访问的数据了。ThreadLocal变量高效地为每一个使用它的线程提供单独的线程局部变量值的副本。每一个线程只能看到与本身相联系的值,而不知作别的线程可能正在使用或修改它们本身的副本。数组
不少人看到这里会容易产生一种错误的印象,感受是否是这个ThreadLocal对象创建了一个相似于全局的map,而后每一个线程做为map的key来存取对应线程本地的value。你看,每一个线程不同,因此他们映射到map中的key应该也不同。实际上,若是咱们后面详细分析ThreadLocal的代码时,会发现不是这样的。它具体是怎么实现的呢? 缓存
##ThreadLocal源码## ThreadLocal类自己定义了有get(), set()和initialValue()三个方法。前面两个方法是public的,initialValue()是protected的,主要用于咱们在定义ThreadLocal对象的时候根据须要来重写。这样咱们初始化这么一个对象在里面设置它的初始值时就用到这个方法。ThreadLocal变量由于自己定位为要被多个线程来访问,它一般被定义为static变量。安全
ThreadLocal有一个ThreadLocalMap静态内部类,你能够简单理解为一个MAP,这个Map为每一个线程复制一个变量的‘拷贝’存储其中。
当线程调用ThreadLocal.get()方法获取变量时,首先获取当前线程引用,以此为key去获取响应的ThreadLocalMap,若是此‘Map’不存在则初始化一个,不然返回其中的变量,代码以下:
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(); }
调用get方法若是此Map不存在首先初始化,建立此map,将线程为key,初始化的vlaue存入其中,注意此处的initialValue,咱们能够覆盖此方法,在首次调用时初始化一个适当的值。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; }
set方法相对比较简单若是理解以上俩个方法,获取当前线程的引用,从map中获取该线程对应的map,若是map存在更新缓存值,不然建立并存储,代码以下:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
对于ThreadLocal在何处存储变量副本,咱们看getMap方法:获取的是当前线程的ThreadLocal类型的threadLocals属性。显然变量副本存储在每个线程中。
/** * 获取线程的ThreadLocalMap 属性实例 */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
上面咱们知道变量副本存放于何处,这里咱们简单说下如何被java的垃圾收集机制收集,当咱们不在使用时调用set(null),此时不在将引用指向该‘map’,而线程退出时会执行资源回收操做,将申请的资源进行回收,其实就是将属性的引用设置为null。这时已经不在有任何引用指向该map,故而会被垃圾收集。
注意:若是ThreadLocal.set()进去的东西原本就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的仍是这个共享对象自己,仍是有并发访问问题。
看到ThreadLocal类中的变量只有这3个int型:
/** * ThreadLocals rely on per-thread linear-probe hash maps attached * to each thread (Thread.threadLocals and * inheritableThreadLocals). The ThreadLocal objects act as keys, * searched via threadLocalHashCode. This is a custom hash code * (useful only within ThreadLocalMaps) that eliminates collisions * in the common case where consecutively constructed ThreadLocals * are used by the same threads, while remaining well-behaved in * less common cases. */ private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. Starts at * zero. */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647;
而做为ThreadLocal实例的变量只有 threadLocalHashCode 这一个,nextHashCode 和HASH_INCREMENT 是ThreadLocal类的静态变量,实际上HASH_INCREMENT是一个常量,表示了连续分配的两个ThreadLocal实例的threadLocalHashCode值的增量,而nextHashCode 的表示了即将分配的下一个ThreadLocal实例的threadLocalHashCode 的值。
如今来看看它的哈希策略。全部ThreadLocal对象共享一个AtomicInteger对象nextHashCode用于计算hashcode,一个新对象产生时它的hashcode就肯定了,算法是从0开始,以HASH_INCREMENT = 0x61c88647
为间隔递增,这是ThreadLocal惟一须要同步的地方。根据hashcode定位桶的算法是将其与数组长度-1进行与操做:key.threadLocalHashCode & (table.length - 1)
。
0x61c88647这个魔数是怎么肯定的呢? ThreadLocalMap的初始长度为16,每次扩容都增加为原来的2倍,即它的长度始终是2的n次方,上述算法中使用0x61c88647
可让hash的结果在2的n次方内尽量均匀分布,减小冲突的几率。
能够来看一下建立一个ThreadLocal实例即new ThreadLocal()时作了哪些操做,从上面看到构造函数ThreadLocal()里什么操做都没有,惟一的操做是这句:
private final int threadLocalHashCode = nextHashCode();
那么nextHashCode()作了什么呢:
private static synchronized int nextHashCode() { int h = nextHashCode; nextHashCode = h + HASH_INCREMENT; return h; }
就是将ThreadLocal类的下一个hashCode值即nextHashCode的值赋给实例的threadLocalHashCode,而后nextHashCode的值增长HASH_INCREMENT这个值。
所以ThreadLocal实例的变量只有这个threadLocalHashCode,并且是final的,用来区分不一样的ThreadLocal实例,ThreadLocal类主要是做为工具类来使用,那么ThreadLocal.set()进去的对象是放在哪儿的呢?
看一下上面的set()方法,两句合并一下成为:
ThreadLocalMap map = Thread.currentThread().threadLocals;
这个ThreadLocalMap 类是ThreadLocal中定义的内部类,可是它的实例却用在Thread类中:
public class Thread implements Runnable { ...... /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; ...... }
这就说明了其实每一个Thread自己就包含了两个ThreadLocalMap对象的引用。这一点很是重要。之后每一个thread要访问他们的local对象时,就是访问存在这个ThreadLocalMap里的value。
ThreadLocalMap是定义在ThreadLocal类内部的私有类,它是采用“开放定址法”解决冲突的hashmap。key是ThreadLocal对象。当调用某个ThreadLocal对象的get或put方法时,首先会从当前线程中取出ThreadLocalMap,而后查找对应的value:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); //拿到当前线程的ThreadLocalMap if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); // 以该ThreadLocal对象为key取value if (e != null) return (T)e.value; } return setInitialValue(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
再看这句:
if (map != null) map.set(this, value);
也就是将该ThreadLocal实例做为key,要保持的对象做为值,设置到当前线程的ThreadLocalMap 中,get()方法一样你们看了代码也就明白了,ThreadLocalMap 类的代码太多了,我就不帖了,本身去看源码吧。 ###天然想法实现### 一个很是天然想法是用一个线程安全的 Map<Thread,Object>
实现:
class ThreadLocal { private Map values = Collections.synchronizedMap(new HashMap()); public Object get() { Thread curThread = Thread.currentThread(); Object o = values.get(curThread); if (o == null && !values.containsKey(curThread)) { o = initialValue(); values.put(curThread, o); } return o; } public void set(Object newValue) { values.put(Thread.currentThread(), newValue); } }
但这是很是naive的:
JDK 的实现恰好是反过来的:
###碰撞解决与神奇的0x61c88647### 既然ThreadLocal用map就避免不了冲突的产生。
碰撞避免和解决 这里碰撞其实有两种类型: (1)只有一个ThreadLocal实例的时候(上面推荐的作法),当向thread-local变量中设置多个值的时产生的碰撞,碰撞解决是经过开放定址法, 且是线性探测(linear-probe)。 (2)多个ThreadLocal实例的时候,最极端的是每一个线程都new一个ThreadLocal实例,此时利用特殊的哈希码0x61c88647大大下降碰撞的概率, 同时利用开放定址法处理碰撞。
神奇的0x61c88647 注意 0x61c88647 的利用主要是为了多个ThreadLocal实例的状况下用的。从ThreadLocal源码中找出这个哈希码所在的地方:
/** * ThreadLocals rely on per-thread linear-probe hash maps attached * to each thread (Thread.threadLocals and inheritableThreadLocals). * The ThreadLocal objects act as keys, searched via threadLocalHashCode. * This is a custom hash code (useful only within ThreadLocalMaps) that * eliminates collisions in the common case where consecutively * constructed ThreadLocals are used by the same threads, * while remaining well-behaved in less common cases. */ private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. * Starts at zero. */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
注意实例变量threadLocalHashCode, 每当建立ThreadLocal实例时这个值都会累加 0x61c88647, 目的在上面的注释中已经写的很清楚了:为了让哈希码能均匀的分布在2的N次方的数组里, 即 Entry[] table
。
下面来看一下ThreadLocal怎么使用的这个 threadLocalHashCode
哈希码的,下面是ThreadLocalMap静态内部类中的set方法的部分代码:
// Set the value associated with key. private void set(ThreadLocal key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {...} ...
key.threadLocalHashCode & (len-1)
这么用是什么意思? 先看一下table数组的长度吧:
/** * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table;
哇,ThreadLocalMap 中 Entry[] table 的大小必须是2的N次方呀(len = 2^N),那 len-1 的二进制表示就是低位连续的N个1, 那 key.threadLocalHashCode & (len-1) 的值就是 threadLocalHashCode 的低N位, 这样就能均匀的产生均匀的分布? 我用python作个实验吧:
>>> HASH_INCREMENT = 0x61c88647 >>> def magic_hash(n): ... for i in range(n): ... nextHashCode = i * HASH_INCREMENT + HASH_INCREMENT ... print nextHashCode & (n - 1), ... print ... >>> magic_hash(16) 7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0 >>> magic_hash(32) 7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0
产生的哈希码分布真的是很均匀,并且没有任何冲突啊, 太神奇了。
##ThreadLocal内存泄漏## 不少人认为:threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用之后,map里面的value却没有被回收.而这块value永远不会被访问到了. 因此存在着内存泄露. 最好的作法是将调用threadlocal的remove方法。
说的也比较正确,当value再也不使用的时候,调用remove的确是很好的作法.但内存泄露一说却不正确. 这是threadlocal的设计的不得已而为之的问题. 首先,让咱们看看在threadlocal的生命周期中,都存在哪些引用吧. 看下图: 实线表明强引用,虚线表明弱引用。
每一个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每一个key都弱引用指向threadlocal. 当把threadlocal实例tl置为null之后,没有任何强引用指向threadlocal实例,因此threadlocal将会被gc回收. 可是,咱们的value却不能回收,由于存在一条从current thread链接过来的强引用. 只有当前thread结束之后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将所有被GC回收。经过源码看下此处的实现,以下:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); // 将当前threadLocal实例做为key else createMap(t, value); } private void set(ThreadLocal key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); // 构造key-value实例 int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); // 构造key弱引用 value = v; } } 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(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
从中能够看出,弱引用只存在于key上,因此key会被回收. 而value还存在着强引用.只有thead退出之后,value的强引用链条才会断掉。一旦某个ThreadLocal对象没有强引用了,它在全部线程内部的ThreadLocalMap中的key都将被GC掉(此时value还未回收),在map后续的get/set中会探测到key被回收的entry,将其 value 设置为 null 以帮助GC,所以 value 在 key 被 GC 后可能还会存活一段时间,但最终也会被回收。这个过程和java.util.WeakHashMap的实现几乎是同样的。
所以ThreadLocal自己是没有内存泄露问题的,一般由它引起的内存泄露问题都是线程只 put 而忘了 remove 致使的,从上面分析可知,即便线程退出了,只要 ThreadLocal 还有强引用,该线程曾经 put 过的东西是不会被回收掉的。 ##ThreadLocal有何用## 不少时候咱们会建立一些静态域来保存全局对象,那么这个对象就可能被任意线程访问到,若是它是线程安全的,这固然没什么说的。然而大部分状况下它不是线程安全的(或者没法保证它是线程安全的),尤为是当这个对象的类是由咱们本身(或身边的同事)建立的(不少开发人员对线程的知识都是只知其一;不知其二,更况且线程安全)。
这时候咱们就须要为每一个线程都建立一个对象的副本。咱们固然能够用ConcurrentMap<Thread, Object>来保存这些对象,但问题是当一个线程结束的时候咱们如何删除这个线程的对象副本呢?
ThreadLocal为咱们作了一切。首先咱们声明一个全局的ThreadLocal对象(final static,没错,我很喜欢final),当咱们建立一个新线程并调用threadLocal.get时,threadLocal会调用initialValue方法初始化一个对象并返回,之后不管什么时候咱们在这个线程中调用get方法,都将获得同一个对象(除非期间set过)。而若是咱们在另外一个线程中调用get,将的到另外一个对象,并且始终会获得这个对象。
当一个线程结束了,ThreadLocal就会释放跟这个线程关联的对象,这不须要咱们关心,反正一切都悄悄地发生了。
(以上叙述只关乎线程,而不关乎get和set是在哪一个方法中调用的。之前有不少不理解线程的同窗老是问我这个方法是哪一个线程,那个方法是哪一个线程,我不知如何回答。)
因此,保存”线程局部变量”的map并不是是ThreadLocal的成员变量, 而是java.lang.Thread的成员变量。也就是说,线程结束的时候,该map的资源也同时被回收。经过以下代码:
ThreadLocal的set,get方法中均经过以下方式获取Map: ThreadLocalMap map = getMap(t); 而getMap方法的代码以下: ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
可见:ThreadLocalMap实例是做为java.lang.Thread的成员变量存储的,每一个线程有惟一的一个threadLocalMap。这个map以ThreadLocal对象为key,”线程局部变量”为值,因此一个线程下能够保存多个”线程局部变量”。对ThreadLocal的操做,实际委托给当前Thread,每一个Thread都会有本身独立的ThreadLocalMap实例,存储的仓库是Entry[] table;Entry的key为ThreadLocal,value为存储内容;所以在并发环境下,对ThreadLocal的set或get,不会有任何问题。如下为”线程局部变量”的存储图:
因为treadLocalMap是java.util.Thread的成员变量,threadLocal做为threadLocalMap中的key值,在一个线程中只能保存一个”线程局部变量”。将ThreadLocalMap做为Thread类的成员变量的好处是: a. 当线程死亡时,threadLocalMap被回收的同时,保存的”线程局部变量”若是不存在其它引用也能够同时被回收。 b. 同一个线程下,能够有多个treadLocal实例,保存多个”线程局部变量”。 c. 同一个threadLocal实例,能够有多个线程使用,保存多个线程的“线程局部变量”。
##ThreadLocal的应用## 咱们在多线程的开发中,常常会考虑到的策略是对一些须要公开访问的属性经过设置同步的方式来访问。这样每次能保证只有一个线程访问它,不会有冲突。可是这样作的结果会使得性能和对高并发的支持不够。在某些状况下,若是咱们不必定非要对一个变量共享不可,而是给每一个线程一个这样的资源副本,让他们能够独立都各自跑各自的,这样不是能够大幅度的提升并行度和性能了吗?
还有的状况是有的数据自己不是线程安全的,或者说它只能被一个线程使用,不能被其余线程同时使用。若是等一个线程使用完了再给另一个线程使用就根本不现实。这样的状况下,咱们也能够考虑用ThreadLocal。一个典型的状况就是咱们链接数据库的时候一般会用到链接池。而对数据库的链接不能有多个线程共享访问。这个时候就须要使用ThreadLocal了。在比较熟悉的两个框架中,Struts2和Hibernate均有采用ThreadLocal变量,并且对整个框架来讲是很是核心的一部分。
Struts2和Struts1的一个重要升级就是对request,response两个对象的解耦,Struts2的Action方法中再也不须要传递request,response参数。可是Struts2不经过方法直接传入request,response对象,那么这两个值是如何传递的呢?
Struts2采用的正是ThreadLocal变量。在每次接收到请求时,Struts2在调用拦截器和action前,经过将request,response对象放入ActionContext实例中,而ActionContext实例是做为”线程局部变量”存入ThreadLocal actionContext中。
public class ActionContext implements Serializable { static ThreadLocal actionContext = new ThreadLocal(); . . .
因为actionContext是”线程局部变量”,这样咱们经过ServletActionContext.getRequest()便可得到本线程的request对象,并且在本地线程的任意类中,都可经过该方法获取”线程局部变量”,而无需值传递,这样Action类既能够成为一个simple类,无需继承struts2的任意父类。
在利用Hibernate开发DAO模块时,咱们和Session打的交道最多,因此如何合理的管理Session,避免Session的频繁建立和销毁,对于提升系统的性能来讲是很是重要的。通常经常使用的Hibernate工厂类,都会经过ThreadLocal来保存线程的session,这样咱们在同一个线程中的处理,工厂类的getSession()方法,便可以屡次获取同一个Session进行操做,closeSession方法可在不传入参数的状况下,正确关闭session。
Hiberante的Session 工具类HibernateUtil,这个类是Hibernate官方文档中HibernateUtil类,用于session管理。以下:
public class HibernateUtil { private static Log log = LogFactory.getLog(HibernateUtil.class); private static final SessionFactory sessionFactory; //定义SessionFactory static { try { // 经过默认配置文件hibernate.cfg.xml建立SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { log.error("初始化SessionFactory失败!", ex); throw new ExceptionInInitializerError(ex); } } //建立线程局部变量session,用来保存Hibernate的Session public static final ThreadLocal session = new ThreadLocal(); /** * 获取当前线程中的Session * @return Session * @throws HibernateException */ public static Session currentSession() throws HibernateException { Session s = (Session) session.get(); // 若是Session尚未打开,则新开一个Session if (s == null) { s = sessionFactory.openSession(); session.set(s); //将新开的Session保存到线程局部变量中 } return s; } public static void closeSession() throws HibernateException { //获取线程局部变量,并强制转换为Session类型 Session s = (Session) session.get(); session.set(null); if (s != null) s.close(); } }
在这个类中,因为没有重写ThreadLocal的initialValue()方法,则首次建立线程局部变量session其初始值为null,第一次调用currentSession()的时候,线程局部变量的get()方法也为null。所以,对session作了判断,若是为null,则新开一个Session,并保存到线程局部变量session中,这一步很是的关键,这也是“public static final ThreadLocal session = new ThreadLocal()”所建立对象session能强制转换为Hibernate Session对象的缘由。
能够看到在getSession()方法中,首先判断当前线程中有没有放进去session,若是尚未,那么经过sessionFactory().openSession()来建立一个session,再将session set到线程中,实际是放到当前线程的ThreadLocalMap这个map中,这时,对于这个session的惟一引用就是当前线程中的那个ThreadLocalMap,而threadSession做为这个值的key,要取得这个session能够经过threadSession.get()来获得,里面执行的操做实际是先取得当前线程中的ThreadLocalMap,而后将threadSession做为key将对应的值取出。这个session至关于线程的私有变量,而不是public的。
显然,其余线程中是取不到这个session的,他们也只能取到本身的ThreadLocalMap中的东西。要是session是多个线程共享使用的,那还不乱套了。
试想若是不用ThreadLocal怎么来实现呢?可能就要在action中建立session,而后把session一个个传到service和dao中,这可够麻烦的。或者能够本身定义一个静态的map,将当前thread做为key,建立的session做为值,put到map中,应该也行,这也是通常人的想法,但事实上,ThreadLocal的实现恰好相反,它是在每一个线程中有一个map,而将ThreadLocal实例做为key,这样每一个map中的项数不多,并且当线程销毁时相应的东西也一块儿销毁了,不知道除了这些还有什么其余的好处。 ###典型使用方式###
// 摘自 j.u.c.ThreadLocalRandom private static final ThreadLocal<ThreadLocalRandom> localRandom = // ThreadLocal对象都是static的,全局共享 new ThreadLocal<ThreadLocalRandom>() { // 初始值 protected ThreadLocalRandom initialValue() { return new ThreadLocalRandom(); } }; localRandom.get(); // 拿当前线程对应的对象 localRandom.put(...); // put
##ThreadLocal产生的问题## 在WEB服务器环境下,因为Tomcat,weblogic等服务器有一个线程池的概念,即接收到一个请求后,直接从线程池中取得线程处理请求;请求响应完成后,这个线程自己是不会结束,而是进入线程池,这样能够减小建立线程、启动线程的系统开销。
因为Tomcat线程池的缘由,我最初使用的”线程局部变量”保存的值,在下一次请求依然存在(同一个线程处理),这样每次请求都是在本线程中取值而不是去memCache中取值,若是memCache中的数据发生变化,也没法及时更新。
解决方案:处理完成后主动调用该业务treadLocal的remove()方法,将”线程局部变量”清空,避免本线程下次处理的时候依然存在旧数据。
**Sturts2是如何解决线程池的问题呢?**因为web服务器的线程是屡次使用的,很显然Struts2在响应完成后,会主动的清除“线程局部变量”中的ActionContext值,在struts2的org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter类中,有这样的代码片断:
finally { prepare.cleanupRequest(request); }
而cleanupRequest方法中有以下代码:
public void cleanupRequest(HttpServletRequest request) { ……//省略部分代码 ActionContext.setContext(null); Dispatcher.setInstance(null); }
因而可知,Sturts2在处理完成后,会主动清空”线程局部变量”ActionContext,来达到释放系统资源的目的。
##ThreadLocal总结## ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每一个线程的中并发访问的数据提供一个副本,经过访问副原本运行业务,这样的结果是耗费了内存,单大大减小了线程同步所带来性能消耗,也减小了线程并发控制的复杂度。
ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。
ThreadLocal和Synchonized都用于解决多线程并发访问。可是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每个线程都提供了变量的副本,使得每一个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通讯时可以得到数据共享。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
固然ThreadLocal并不能替代synchronized,它们处理不一样的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。
总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。概括了两点:
##ThreadLocal建议##