ThreadLocal字面意思是线程局部变量,它为每个线程提供了独立的互不干扰的局部变量。html
下面以一个简单的例子来简单介绍下ThreadLocal的使用:java
public class ThreadLocalDemo2 {
public static class MyRunnable implements Runnable {
private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 1;
}
};
@Override
public void run() {
threadLocal.set((int) (Math.random() * 100D));
System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable, "A");
Thread t2 = new Thread(myRunnable, "B");
t1.start();
t2.start();
}
/**
B:48
A:32
即:线程A与线程B中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();
}
public static native Thread currentThread();
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
复制代码
注意:web
ThreadLocal所存储的变量的实际值是经过ThreadLocalMap结构存在Thread类的成员变量上的,也就是说每个Java线程,Thread类的对象实例,都有一个本身的ThreadLocalMap。数组
ThreadLocalMap是ThreadLocal.java中的一个静态内部类,它是一个为了维护线程局部变量(ThreadLocal)定制化的哈希表。浏览器
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
private Entry[] table;
...
}
复制代码
这个ThreadLocalMap的Key为泛型类ThreadLocal的实例,Value为要存储的ThreadLocal变量T。tomcat
实际ThreadLocal变量T与ThreadLocal的实例一块儿做为一个Entry,存储在table里面。bash
注意到这里的ThreadLocalMap.Entry是继承WeakReference使得做为Key ThreadLocal的实例为一个弱引用。那么,在ThreadLocal的实例仅存在弱引用的且被GC线程扫描到的时候,就会GC回收掉threadLocal实例的内存。这个时候,对应的Key值就为null了。服务器
这里设计为Map是因为一个线程可能有多个线程局部变量即多个ThreadLocal的对象实例。dom
上面说到ThreadLocalMap,是一个定制化的哈希表。既然是哈希表就须要解决哈希冲突的问题。对于java.util.HashMap,解决冲突的方式是拉链法。ide
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); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } 复制代码
而ThreadLocalMap解决冲突的方式是开放定址法 。
以set操做为例,简单的说就是经过Key作一次Hash以后,若是发现哈希结果对应位置的key和当前要set的key不一致,就日后面找,直到找到一个空的位置。
那么,可不可能找不到呢?
答案是不可能的。
很差的反作用 因为使用了开放定址法,致使ThreadLocalMap的set,get,remove操做都不能在一次哈希寻址肯定找到正确的位置。须要再花费O(n)的时间进行二次寻址去找到空位置或者是能获取、删除的位置。
好的反作用 JDK源码做者经过另外一种方式利用了开放定址法带来的二次寻址的循环。在set和get方法的二次寻址的循环过程当中,若是发现了stale entry(即key值为空,可是Entry值非空,这里也能够理解为value值非空)的位置,就会进行清理。
ThreadLocalMap -> set -> replaceStaleEntry -> expungeStaleEntry
ThreadLocalMap -> get -> getEntry -> getEntryAfterMiss -> expungeStaleEntry
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
...
}
复制代码
注意:这种方式并不能保证,每次ThreadLocalMap.set 或者 get操做都能清除掉全部的key被回收的entry节点。举一个极端的反例,ThreadLocalMap有key为null的entry,可是get操做的第一次hash就直接找到了正确的位置,并无进行二次寻找。那么,此时就没法进行清除。
上面提到,ThreadLocalMap::Entry::ThreadLocal是一个弱引用。那么,为何要用WeakReference呢?
这里,咱们反向思考下,若是不使用弱引用,而使用强引用。那么,在线程的整个生命周期内,全部定义的ThreadLocal变量都一直存在,即便是用户已经再也不使用ThreadLocal变量了,这是由于下面2条的引用关系链一直存在:
ThreadLocalRef->Thread->ThreadLocal->ThreadLocalMap->Entry->key
ThreadLocalRef->Thread->ThreadLocal->ThreadLocalMap->Entry->value
那么,若是用户不进行手动的ThreadLocalMap::remove,所占用的空间就一直释放不掉。
综上,我理解的使用ThreadLocalMap->Entry->key(即ThreadLocal)使用弱引用的缘由是为了在用户没有进行手动的ThreadLocalMap::remove状况下,也能让系统有方法在set,get的时候进行部分的资源清理。虽然,JVM只清理了key,可是后续JDK源码设计提供了清理value以及整个entry的机制(将value和entry在ThreadLocalMap中的强引用给消除掉)。可是,这机制不必定能用上。
So,每次肯定ThreadLocal再也不使用后,都要手动调用它的remove()方法进行数据清除。
否则,就可能会出现内存泄露。
在ThreadLocal变量仅持有弱引用的时候,若是经历了GC就会被清除掉内存。而后,ThreadLocalMap的ThreadLocal key就变成null了。可是,对应的value因为上面所写的强引用关系链还一直存在,就没发被回收。因而,就发生了value值没发被获取和使用,可是又没法被回收的状况,即内存泄漏。
举几个例子说明一下:
从这个简单的访问过程咱们看到正好这个 Session 是在处理一个用户会话过程当中产生并使用的,若是单纯的理解一个用户的一次会话对应服务端一个独立的处理线程,那用 ThreadLocal 在存储 Session ,简直是再合适不过了。可是例如 tomcat 这类的服务器软件都是采用了线程池技术的,并非严格意义上的一个会话对应一个线程。并非说这种状况就不适合 ThreadLocal 了,而是要在每次请求进来时先清理掉以前的 Session ,通常能够用拦截器、过滤器来实现。
最后,以为写的不错的同窗麻烦点个赞,支持一下呗^_^~