参考:http://www.iteye.com/topic/103804
http://www.iteye.com/topic/777716java
为了解释ThreadLocal类的工做原理,必须同时介绍与其工做甚密的其余几个类多线程
首先,在Thread类中有一行:并发
/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
其中ThreadLocalMap类的定义是在ThreadLocal类中,真正的引用倒是在Thread类中。同时,ThreadLocalMap中用于存储数据的entry定义:函数
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
从中咱们能够发现这个Map的key是ThreadLocal类的实例对象,value为用户的值,并非网上大多数的例子key是线程的名字或者标识。ThreadLocal的set和get方法代码:源码分析
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
其中的getMap方法:this
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
给当前Thread类对象初始化ThreadlocalMap属性:线程
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
到这里,咱们就能够理解ThreadLocal到底是如何工做的了设计
public class Son implements Cloneable{ public static void main(String[] args){ Son p=new Son(); System.out.println(p); Thread t = new Thread(new Runnable(){ public void run(){ ThreadLocal<Son> threadLocal = new ThreadLocal<>(); System.out.println(threadLocal); threadLocal.set(p); System.out.println(threadLocal.get()); threadLocal.remove(); try { threadLocal.set((Son) p.clone()); System.out.println(threadLocal.get()); } catch (CloneNotSupportedException e) { e.printStackTrace(); } System.out.println(threadLocal); }}); t.start(); } }
输出:code
Son@7852e922 java.lang.ThreadLocal@3ffc8195 Son@7852e922 Son@313b781a java.lang.ThreadLocal@3ffc8195
也就是若是把一个共享的对象直接保存到ThreadLocal中,那么多个线程的ThreadLocal.get()取得的仍是这个共享对象自己,仍是有并发访问问题。 因此要在保存到ThreadLocal以前,经过克隆或者new来建立新的对象,而后再进行保存。
ThreadLocal的做用是提供线程内的局部变量,这种变量在线程的生命周期内起做用。做用:提供一个线程内公共变量(好比本次请求的用户信息),减小同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度,或者为线程提供一个私有的变量副本,这样每个线程均可以随意修改本身的变量副本,而不会对其余线程产生影响。对象
如何实现一个线程多个ThreadLocal对象,每个ThreadLocal对象是如何区分的呢?
查看源码,能够看到:
private final int threadLocalHashCode = nextHashCode(); private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
对于每个ThreadLocal对象,都有一个final修饰的int型的threadLocalHashCode不可变属性,对于基本数据类型,能够认为它在初始化后就不能够进行修改,因此能够惟一肯定一个ThreadLocal对象。
可是如何保证两个同时实例化的ThreadLocal对象有不一样的threadLocalHashCode属性:在ThreadLocal类中,还包含了一个static修饰的AtomicInteger([əˈtɒmɪk]提供原子操做的Integer类)成员变量(即类变量)和一个static final修饰的常量(做为两个相邻nextHashCode的差值)。因为nextHashCode是类变量,因此每一次调用ThreadLocal类均可以保证nextHashCode被更新到新的值,而且下一次调用ThreadLocal类这个被更新的值仍然可用,同时AtomicInteger保证了nextHashCode自增的原子性。
为何不直接用线程id来做为ThreadLocalMap的key?
这一点很容易理解,由于直接用线程id来做为ThreadLocalMap的key,没法区分放入ThreadLocalMap中的多个value。好比咱们放入了两个字符串,你如何知道我要取出来的是哪个字符串呢?
而使用ThreadLocal做为key就不同了,因为每个ThreadLocal对象均可以由threadLocalHashCode属性惟一区分或者说每个ThreadLocal对象均可以由这个对象的名字惟一区分(下面的例子),因此能够用不一样的ThreadLocal做为key,区分不一样的value,方便存取。
public class Son implements Cloneable{ public static void main(String[] args){ Thread t = new Thread(new Runnable(){ public void run(){ ThreadLocal<Son> threadLocal1 = new ThreadLocal<>(); threadLocal1.set(new Son()); System.out.println(threadLocal1.get()); ThreadLocal<Son> threadLocal2 = new ThreadLocal<>(); threadLocal2.set(new Son()); System.out.println(threadLocal2.get()); }}); t.start(); } }
ThreadLocal的内存泄露问题
根据上面Entry方法的源码,咱们知道ThreadLocalMap是使用ThreadLocal的弱引用做为Key的。下图是本文介绍到的一些对象之间的引用关系图,实线表示强引用,虚线表示弱引用:
如上图,ThreadLocalMap使用ThreadLocal的弱引用做为key,若是一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,若是当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远没法回收,形成内存泄露。
ThreadLocalMap设计时的对上面问题的对策:
ThreadLocalMap的getEntry函数的流程大概为:
关于ThreadLocalMap内部类的简单介绍 初始容量16,负载因子2/3,解决冲突的方法是再hash法,也就是:在当前hash的基础上再自增一个常量。