概述
一个线程可以设置多个线程局部变量,多个线程局部变量ThreadLocal保存在Thread#ThreadLocalMap中。注意,通过ThreadLocal#get()方法保存的值,并不是保存在ThreadLocal的内部变量中,而是以该ThreadLocal为key(其实是以WeakReference<ThreadLocal>为key),以该值为value,保存在currenthread的ThreadLocalMap中。
Thread
每个Thread中都引用了一个ThreadLocalMap。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap
ThreadLocalMap使用线性探测法解决hash冲突
构造函数
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化Entry[]数组
table = new Entry[INITIAL_CAPACITY];
//使用key.hashCode&(len -1)获取key在Entry[]中的下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//Entry[]中对应下标的元素赋值
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
get方法
/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
* @param key the thread local object
* @return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal<?> key) {
//使用key.hashCode&(len -1)获取key在Entry[]中的下标
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//如果没有发生hash冲突,值相等,则直接返回,否则线性探测寻找下一个entry
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
发生冲突,开始线性探测:循环寻找下一个entry的下标,直至值相等才返回。
/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
//循环寻找下一个entry的下标,直至值相等才返回
while (e != null) {
ThreadLocal<?> k = e.get();
//如果值相等,返回
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
//否则获取下一个entry的下标
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
set方法
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
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;
//使用key.hashCode&(len -1)获取key在Entry[]中的下标
int i = key.threadLocalHashCode & (len-1);
//从该下标位置处开始线性探测,直至寻找到一个下标位置为null的Entry为止
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;
}
}
//在下标位置为null的Entry处赋值
tab[i] = new Entry(key, value);
int sz = ++size;
//赋值后判断是否需要重哈希扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
ThreadLocalMap的Entry
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap清除过期Entry(避免内存泄漏)
上文也提到了,Entry继承自WeakReference,大家都知道WeakReference(弱引用)的特性,只要从根集出发的引用中没有有效引用指向该对象,则该对象就可以被回收,这里的有效引用并不包含WeakReference,所以弱引用不影响对象被GC。
这里被WeakReference引用的对象是哪个呢?可以看Entry的构造方法,很容易看出指的是ThreadLocal自身,也就是说ThreadLocal自身的回收不受ThreadLocalMap的这个弱引用的影响,让用户减轻GC的烦恼。
但是不用做些什么吗?这么简单?
其实不然,ThreadLocalMap还做了其他的工作,试想一下,ThreadLocal对象如果外界没有有效引用,是能够被GC,但是Entry不能自动被GC,Entry还被ThreadLocalMap的table数组强引用着呢,Entry和Entry.value都无法被回收。
所以ThreadLocalMap该做点什么?
我看看ThreadLocalMap的expungeStaleEntry这个方法,这个方法在ThreadLocalMap的get、set、remove、rehash等方法都会调用到,看下面标红的两处代码,第一处是将remove的entry赋空,第二次处是找到已经被GC的ThreadLocal,然后会清理掉table数组对entry的引用。这样entry在后续的GC中就会被回收。
/**
* Expunge a stale entry by rehashing any possibly colliding entries
* lying between staleSlot and the next null slot. This also expunges
* any other stale entries encountered before the trailing null. See
* Knuth, Section 6.4
*
* @param staleSlot index of slot known to have null key
* @return the index of the next null slot after staleSlot
* (all between staleSlot and this slot will have been checked
* for expunging).
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
//让指定下标位置的Entry.value和Entry能被回收
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
ThreadLocal
get()方法
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
//获取currentThread的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//获取ThreadLocalMap中该ThreadLocal对应的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
set()方法
**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
//获取currentThread的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//设置该ThreadLocal对应的value
map.set(this, value);
else
createMap(t, value);
}
remove()方法
如果数据初始化好之后,一直不调用get、set等方法,这样Entry就一直不能回收,导致内存泄漏。所以一旦数据不使用最好主动remove。
/**
* Removes the current thread's value for this thread-local
* variable. If this thread-local variable is subsequently
* {@linkplain #get read} by the current thread, its value will be
* reinitialized by invoking its {@link #initialValue} method,
* unless its value is {@linkplain #set set} by the current thread
* in the interim. This may result in multiple invocations of the
* {@code initialValue} method in the current thread.
*
* @since 1.5
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocal的使用示例
Hibernate使用ThreadLocal管理多线程访问的部分代码如下
public static final ThreadLocal session = new ThreadLocal();
public static Session currentSession() {
Session s = (Session)session.get();
//open a new session,if this session has none
if(s == null){
s = sessionFactory.openSession();
session.set(s);
}
return s;
}
1。 初始化一个ThreadLocal对象,ThreadLocal有三个成员方法 get()、set()、initialvalue()。
如果不初始化initialvalue,则initialvalue返回null。
3。session的get根据当前线程返回其对应的线程内部变量,也就是我们需要的net.sf.hibernate.Session(相当于对应每个数据库连接).多线程情况下共享数据库链接是不安全的。ThreadLocal保证了每个线程都有自己的s(数据库连接)。
5。如果是该线程初次访问,自然,s(数据库连接)会是null,接着创建一个Session,具体就是行6。
6。创建一个数据库连接实例 s
7。保存该数据库连接s到ThreadLocal中。
8。如果当前线程已经访问过数据库了,则从session中get()就可以获取该线程上次获取过的连接实例。