一、实例代码
先来看一个使用ThreadLocal的实例,然后再开始我们的讲解
class Tools6 {
public static ThreadLocal<Integer> intLocal = new ThreadLocal<>();
public static ThreadLocal<String> strLocal = new ThreadLocal<>();
}
class ThreadA6 extends Thread {
public void set() {
Tools6.intLocal.set(123);
Tools6.strLocal.set("luwenhe");
}
public void get() {
Integer integer = Tools6.intLocal.get();
String s = Tools6.strLocal.get();
System.out.println("int: " + integer + " str: " + s);
}
@Override
public void run() {
System.out.println("------" + Thread.currentThread().getName() + "------");
System.out.print("赋值前:");
get();
set();
System.out.print("赋值后:");
get();
}
}
class ThreadB6 extends Thread {
public void set() {
Tools6.intLocal.set(456);
Tools6.strLocal.set("luwenhe456");
}
public void get() {
Integer integer = Tools6.intLocal.get();
String s = Tools6.strLocal.get();
System.out.println("int: " + integer + " str: " + s);
}
@Override
public void run() {
System.out.println("------" + Thread.currentThread().getName() + "------");
System.out.print("赋值前:");
get();
set();
System.out.print("赋值后:");
get();
}
}
public class Run6 {
public static void main(String[] args) throws InterruptedException {
ThreadA6 threadA6 = new ThreadA6();
threadA6.setName("AAA");
threadA6.start();
Thread.sleep(2000);
ThreadB6 threadB6 = new ThreadB6();
threadB6.setName("BBB");
threadB6.start();
}
}
结果是:
------AAA------
赋值前:int: null str: null
赋值后:int: 123 str: luwenhe
------BBB------
赋值前:int: null str: null
赋值后:int: 456 str: luwenhe456
从结果可以很清晰的看到,即使在线程 AAA 中已经用 intLocal 和 strLocal 这两个对象赋了值了,但是当我们在线程 BBB 中还没赋值之前得到的两个值却是 null 的,说明在几个线程中,虽然各自线程都用同样的 ThreadLocal 对象对数据进行操作,但是 set 和 get 在每个线程中都是使用的,类似两张平行的桥,每个线程各走各的,不会有联系
JDK 的文档是这么说的:
This class provides thread-local variables. These variables differ from
their normal counterparts in that each thread that accesses one (via its
{@code get} or {@code set} method) has its own, independently initialized
copy of the variable. {@code ThreadLocal} instances are typically private
static fields in classes that wish to associate state with a thread (e.g.,
a user ID or Transaction ID).
解释一下:该类提供线程局部变量。这些变量不同于它们的正常对应关系,即每一个线程都有它自己的、独立初始化的变量的拷贝。
ThreadLocal 提供了线程的局部变量,每个线程都可以通过set()
和get()
来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离~。
简要言之:往ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。
二、具体实现
在 Thread 类中,有这么一个变量
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
...
}
该变量 inheritableThreadLocals 是由 ThreadLocal 类的内部类 ThreadLocalMap 创建的,先记住这个变量,然后再来看具体的 ThreadLocal 类
先来看 ThreadLocal 的 set() 方法吧
public void set(T value) {
// 得到当前线程对象
Thread t = Thread.currentThread();
// 通过线程对象得到 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
// 如果 map 对象存在,就将当前 ThreadLocal 的对象作为 key,要存储的值作为 value 放到 map 的键值对中去
if (map != null)
map.set(this, value);
//如果 map 对象不存在,就在当前线程中创建一共 ThreadLocalMap 类,传入的 key 是当前线程对象 t,value 是要存储的值
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
//通过当前线程的对象 t,来获取当前线程对应的 ThreadLocalMap 对象
return t.threadLocals;
}
通过 getMap()
可以看到,该方法接受的是当前线程的对象 t,然后返回的是根据对象 t 来获取的 ThreadLocalMap 类,ThreadLocalMap 是 ThreadLocal 的内部类,说明每个线程都有各自的一个 ThreadLocalMap 内部类
先来看 createMap 方法
//创建 ThreadLocalMap 类的方法
void createMap(Thread t, T firstValue) {
//this 为 ThreadLocal 对象,firstValue 为传入的值
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
该方法接受传入当前的线程对象 t 作为 key,和存储的值作为 value,然后用传入的线程对象 t 创建 ThreadLocalMap 类,ThreadLocalMap 的键是 ThreadLocal 的对象,值还是传入的值,说明每一个线程 Thread 都有自己的 ThreadLocalMap 类
ThreadLocalMap 类是 ThreadLocal 类的一共内部类,用 Entry 类来存储,该 Entry 类的 key 是当前线程的 ThreadLocal 对象,value 是传入的值
//ThreadLocal 类的内部类
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
//key 是 ThreadLocal 对象,value 是传入的值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private final int threadLocalHashCode = nextHashCode();
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
//实例化一个新的 ThreadLocalMap 对象
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化一个大小为16的 Entry 数组
table = new Entry[INITIAL_CAPACITY];
//计算出传入的值在 Entry 数组中的位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//将传入的值放入到 Entry 数组的指定位置
table[i] = new Entry(firstKey, firstValue);
//默认的数组容量为 1
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
因为我们在每个 Thread 类中都有一个 inheritableThreadLocals 变量,该变量对应的是 ThreadLocal 的内部类 ThreadLocalMap,该内部类里面存放了一个 Entry 数组,Entry 数组用来保存一个 key-value 键值对,key 是 ThreadLocal 对象,每个 ThreadLocal 对象都有一个 threadLocalHashCode
值,每初始化一个 ThreadLocal 对象,该值就会增加一个固定大小 0x61c88647
在来看 ThreadLocalMap 的 set() 方法
private void set(ThreadLocal<?> key, Object value) {
//将旧数组赋值给新的 Entry 数组
Entry[] tab = table;
int len = tab.length;
//确定值插入的位置
int i = key.threadLocalHashCode & (len-1);
//遍历 Entry 数组,如果当前位置的 Entry 数组不为空
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//得到当前位置 Entry 键值对的 key,即 ThreadLocal 对象
ThreadLocal<?> k = e.get();
//如果当前 Entry 的 key 和传入的 key 相同,则用的新的值替换旧的值
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//如果当前位置的 Entry 数组为空,则在当前位置新创建了一个 Entry 键值对
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
先根据 ThreadLocal 中的 threadLocalHashCode 变量的值,定位到 Entry 数组中的位置,过程如下:
-
如果当前位置的 Entry 数组为空,就新创建一个 Entry 对象放到位置 i 上,key 是当前线程中传入的 ThreadLocal 对象,value 是在当前线程中传入的值
-
如果当前位置的 Entry 数组不为空:
2.1. 如果这个 Entry 对象的 ThreadLocal 对象和设置的 ThreadLocal 对象一样,那么就用新传入的值覆盖旧的值
2.2. 位置i的Entry对象,和即将设置的key没关系,那么只能找下一个空位置
可以看到,在同一个线程中,每创建一个 ThreadLocal 对象,就产生一个新的 Entry 节点,比如在上面的例子中,我们创建了两个 ThreadLocal 对象 intLocal 和 strLocal,当我们在线程 AAA 和线程 BBB 中使用以下语句时
//线程 AAA
Tools6.intLocal.set(123);
Tools6.strLocal.set("luwenhe");
//线程 BBB
Tools6.intLocal.set(456);
Tools6.strLocal.set("luwenhe456");
第一次在线程 AAA 中使用 set() 方法时,在 ThreadLocal 的 ThreadLocalMap 的 Entry 数组中,以 intLocal 对象为 key,123 为 value 创建一个位置为 3 的 Entry 对象,第二次,因为使用的是 strLocal 对象,因此在 Entry 数组中以 strLocal 为 key,luwenhe 为 value 创建一个位置为 10 的新的对象
当我们在线程 BBB 使用 set() 方法时,其实方法是一样的,不过这时是在线程 BBB 的 ThreadLocal 的 ThreadLocalMap 的 Entry 数组中,同时,两个 key 和线程 AAA 中是一样的,都是使用的 intLocal 和 strLocal
再来看 get() 方法
public T get() {
//得到当前线程对象 t
Thread t = Thread.currentThread();
//通过线程对象 t 得到 ThreadLocalMap 对象 map
ThreadLocalMap map = getMap(t);
//如果 map 对象不为空
if (map != null) {
//根据当前线程的 ThreadLocal 对象,得到 ThreadLocalMap 中的 Entry 对象
ThreadLocalMap.Entry e = map.getEntry(this);
//如果该 Entry 对象不为空,则直接得到 Entry 中的 value
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果 map 为空,使用 ThreadLocalMap 的初始值,初始 value 为 null
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
//根据 ThreadLocal 对象的 hash 值,定位到以该对象为 key 值的 Entry 对象在数组中的位置
int i = key.threadLocalHashCode & (table.length - 1);
//得到具体位置的 Entry 对象
Entry e = table[i];
//如果该位置的 Entry 对象的 key 和 get 到的 key 一致,则直接返回这个 Entry 对象
if (e != null && e.get() == key)
return e;
//否则,继续判断下一个位置
else
return getEntryAfterMiss(key, i, e);
}
二、参考
https://segmentfault.com/a/1190000014152795
http://www.cnblogs.com/dolphin0520/p/3920407.html
https://juejin.im/post/5a64a581f265da3e3b7aa02d