什么是ThreadLocal?
ThreadLocal提供了线程独有的局部变量存储能力,可以在整个线程存活的过程中随时取用。
ThreadLocal是线程间隔离的?
ThreadLoca中线程的局部变量如何存储的?
ThreadLocal实现原理
Thread存储线程独有的变量(存储多个变量(值)时,可以创建多个ThreadLocal用来存储),以供在整个线程存活过程中取用。这种能力由ThreadLocal中的ThreadLocalMap实现。Thread在存储自己独有变量时,ThreadLocal实例在该Thread环境内使用set方法实现变量(值)存储。(继续探究原理)实际是静态内部类ThreadLocalMap将该变量(值)以key=当前线程的ThreadLocal实例,value=变量(值)的键值对对象Entry形式存储到了ThreadLocalMap下的数组中。
为什么一个ThreadLocal在同一线程中只能保存一个变量?在另一个线程可以再次保存一个变量?ThreadLocal和Thread是多对多关系。同Thread下,Thread和ThreadLocalMap是一对一关系。同Thread下,ThreadLocal和ThreadLocalMap是多对一关系。
ThreadLocal在保存变量时,是以当前ThreadLocal实例作为key,
回答了第一个问题
。而保存变量的"容器"是个数组。数组元素是(其实只是个封装的对象而已)Thread.currentThread().TreadLocalMap。当保存一变量时会判断当前线程下的TreadLocalMap是否为空,为空则会从新创建当前线程下的TreadLocalMap。因此回答了第二个问题
。
/// 定义两个ThreadLocal、 开启两个线程进行验证 <Android API 30 Platform>
private static ThreadLocal<String> local = new ThreadLocal<>();
private static ThreadLocal<String> localNext = new ThreadLocal<>();
public static void threadLocal() {
new Thread(()->{
// local.set方法,在存放值时,其实是放到了此时local所在线程ThreadLocal静态内部类ThreadLocalMap中了。
local.set("hello");
System.out.println("获取线程一保存的值= "+ local.get());
// 这行说明一个Thread中可以有多个ThreadLocal用来存储变量。
localNext.set("hello-next");
System.out.println("获取线程二保存的值= "+ localNext.get());
// 这行代码又使用local.set做了不同变量值存储。结果是原先的值被替换。
// 原因是,存储值具体实现其实是在ThreadLocalMap中。其内部实现使用Entry数组存储kye-value键值对的对象Entry。
// 键值对对象存储,value=将要存储的变量(值),key=ThreadLocal实例
// 在ThreadLocalMap内部存储过程,若存储的键值对对象entry已存在,且key相同则value值覆盖。否则,创建entry对象存储到数组中;
// 因此,同一个local中存储变量(值)多次存储将会被覆盖,且存储的变量(值)只有一个。
local.set("world");
System.out.println("获取线程一重新保存后的值= "+ local.get());
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 而这里local.get,则会在此时local所在的当前线程中去获取ThreadLocal中存储的值。
// 显然,这并没有做过任何值存储过程,当然local.get==null
System.out.println("从新开的线程中获取上一个线程local存储的值= "+ local.get());
}).start();
}
/**结果*/
获取线程一保存的值= hello
获取线程二保存的值= hello-next
获取线程一重新保存后的值= world
从新开的线程中获取上一个线程存储的值= null
从上面执行结果可知,ThreadLocal是线程间隔离的。ThreadLocal中线程的局部变量又是如何存储的——借助ThreadLocal实现局部变量存储。
接下来进入源码看下这俩是如何实现的~
// Thread.java
public class Thread implements Runnable {
.....
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null; // ThreadLocalMap 是 ThreadLocal的静态内部类
在Thead中有定义ThreadLocalMap静态内部类。并且在ThreadLocal中,在执行存储ThreadLocal.set (T value)时,先获取当前环境中的线程,并由线程获取ThreadLocalMap实例。若ThreadLocalMap为空,则会先去初始化(创建)。并将ThreadLocalMap实列赋值给Thread中定义的ThreadLocal.ThreadLocalMap成员变量(因此)。若ThreadLocalMap非空,则通过Thread拿到ThreadLocalMap的实例,然后将要存储的变量(值)存储到ThreadLocalMap实例中。
public void set(T value) {
Thread t = Thread.currentThread(); // 获取当前线程,同Thread下,只有一个ThreadLocalMap
ThreadLocalMap map = getMap(t); // 若ThreadLocalMap为空,则会先去初始化(创建)
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) { // ThreadLocalMap实例为空,则会先去初始化(创建)
// 赋值ThreadLocalMap实例给Thread成员变量 ThreadLocal.ThreadLocalMap threadLocals
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
从上面描述看,每个新建的Thead实例,他们的成员变量Threadlocal必定是互相独立的。因此,ThreadLocal是线程间隔离的。
再看存储过程,由ThreadLocal的set (T value)方法执行存储,进而将存储交给ThreadLocalMap方法set(ThreadLocal<?> key, Object value)。在ThreadLocalMap方法内部维护了一个Entry[]数组,继而该数组会执行Thread局部变量的实际存储。存储方式,是通过当前ThreadLocal作为key并通过该key值执行int i = key.threadLocalHashCode & (len-1);
获取变量将要存储在数组中的位置下标。
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 在ThreadLocalMap内部存储过程,若存储的键值对对象entry已存在,且key相同则value值覆盖。
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;
}
}
// 否则,创建entry对象存储到数组中;
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
获取到下标之后,会先判断下标位置是否存在节点Entry。若存在则比较Entry的key(即ThreadLocal)是否与将要存入的变量的key是否相同。相同则覆盖,不同则新建Entry并存储。