本篇文章主要讲解Java并发中的一个重要知识点—ThreadLocal类
ThreadLocal简介
ThreadLocal是一个本地线程副本变量类,它也是线程内部类的存储类,可以在指定线程内部存储数据,且只有指定的线程可以获取存储的数据。
ThreadLocal提供了线程内存储变量的能力,这些变量在每一个线程中都是相互独立的,通过set和get方法可以得到当前线程对应的值。
ThreadLoacl的相关方法
1.set()方法
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//存储的数据结构类型,获取当前线程创建的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//如果存在map就直接set,没有则创建map并set
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
从set()方法可以看到:
1).每次set时都先获取当前的线程,在当前线程的基础上进行操作。
2).获取该线程对象的ThreadLocalMap
3).如果ThreadLocalMap存在,就直接set,没有就先创建再set
看下ThreadLocalMap
static class ThreadLocalMap {
/**
* 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;
}
}
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
}
看完ThreadLocalMap后发现:ThreadLocalMap是ThreadLocal的一个静态内部类,其中又创建了静态类Entry用来存储变量,且Entry中的key是ThreadLocal的对象,value在ThreadLocal确定的下标位置。
再看一下getMap()方法和createMap()方法
//ThreadLocal类中获取Thread类中的ThreadLocalMap对象
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//创建map
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
从getMap()可以直接获取当前线程的局部变量threadLocals(ThreadLocalMap类型)。
从createMap()方法可知:创建map时,实例化一个新的ThreadLocalMap,并赋值给当前线程的成员变量threadLocals。
看下threadLocals(ThreadLocalMap类型)的创建
ThreadLocal.ThreadLocalMap threadLocals = null;
threadLocals是ThreadLocal中的静态内部类ThreadLocalMap类型的一个对象,初始化为null。
再看下ThreadLocalMap的set方法就大功告成啦!
//ThreadLocalMap的set方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//获取 hash 值,用于数组中的下标
int i = key.threadLocalHashCode & (len-1);
//如果数组该位置有对象则进入
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//k 相等则覆盖旧值
if (k == key) {
e.value = value;
return;
}
//此时说明此处 Entry 的 k 中的对象实例已经被回收了,需要替换掉这个位置的 key 和 value
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//创建 Entry 对象
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
//获取 Entry
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
}
其实,通过源码可知,ThreadLocalMap的一些方法和HashMap的方法很类似,都是先获取key的hash值,确定key在table中的索引位置,然后再判断set需要修改的位置和当前获取的位置是否一致,如果一致说明找到数组位置了则覆盖对应的值。
总结一下主要步骤如下:
1).创建数组
2).获取key的hash值,作为数组的下标
3).如果数组当前位置有对象,获取此位置
4).判断key和当前数组位置是不是同一个位置,如果是同一个位置,则说明找到此位置的值,进行覆盖。
看到这里大家可能很混乱,为了研究ThreadLocal的set()方法,竟然带出了一系列的方法,那么,如何将它们整合到一起呢?
我们还是回到最初的ThreadLocal的set()方法,一步步解读:
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//存储的数据结构类型
ThreadLocalMap map = getMap(t);
//如果存在map就直接set,没有则创建map并set
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
解读步骤:
1.调用ThreadLocal的set()方法时,会先获取当前线程。
2.获取当前线程ThreadLocal的静态内部类,通过getMap()方法获取,getMap()方法返回的是当前线程threadLocals(ThreadLocalMap类型),然后在此map上进行值的存储方式为Entry<ThreadLocal实例对象,副本变量>。例如开启数据库连接时为set(getConnection);
存储为:
Entry<ThreadLocal实例,getConnection()>,此时,每个线程都进行各自的数据库连接。
3.如果存在ThreadLocalMap的对象,就直接set
4.如果没有创建ThreadLocalMap的对象,则使用当前线程创建,使用creatMap()方法,实例化一个新的ThreadLocalMap,并赋值给当前线程的threadLocals。
总结:
1.ThreadLocal在每个线程中都创建了一个ThreadLocalMap对象,且所有的操作都在这个对象里。
2.ThreadLocalMap是ThreadLocal的静态内部类,用Entry来进行存储。
3.调用ThreadLocal的set()方法时就是对ThreadLocalMap进行设置值,key是ThreadLocal对象。
ThreadLocal、ThreadLocalMap和Thread的关系
某个线程运行->线程为每个ThreadLocal创建了一个ThreadLocalMap对象->ThreadLocalMap中用Entry来进行存储,key为ThreadLocal的引用,value是对应的值。
2.get()方法
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();
}
看完set()方法,发现get()方法简单了许多,其中大体和set()方法类似,从Entry中获取变量是使用map.getEntry()方法。
总结:我们使用ThreadLocal类是为了使得每个线程在处理各自的变量时互不干扰,而ThreadLocal的互不干扰就体现在:每次调用其方法时都先获取当前的线程,返回当前线程的ThreadLocalMap,实际上ThreadLocal并不会存储值,真正存储值得是它的静态内部类ThreadLocalMap,而ThreadLocal调用set()和get()方法时,也会传递给ThreadLocalMap进行对应的操作,真正实现了一个线程操作线程自己的局部变量。
最重要的一点:ThreadLocal每次都会获取当前线程的ThreadLocalMap,然后在此map的基础上进行数据的存储,由于map都是当前线程创建的,数据自然就是当前线程所拥有的,与其它线程互相独立。