学习最重要的一点就是回顾总结,今天就总结一下ThreadLocal吧!嘿嘿嘿(猥琐的笑一笑)。
初识ThreadLocal
只记得ThreadLocal位于java.lang包内,被称作线程本地变量。名字也是很生动的。当你创建一个ThreadLocal变量的时候,不同线程操作这个TheadLocal变量(设置或者读取),都只会影响到本线程里面的值。而多个线程对一个普通变量操作的话,就会相互影响,存在并发问题。
简单看一个ThreadLocal的例子
//创建一个ThreadLocal变量
ThreadLocal num = new ThreadLocal<>();
//启动两个线程对这个变量进行修改
new Thread(()->{
num.set(100);
try {
//两个线程都小憩一会,保证两个线程都操作了ThreadLocal变量后,再输出。
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " : " + num.get());
},"t1").start();
new Thread(()->{
num.set(200);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " : " + num.get());
},"t2").start();
//输出结果
t2 : 200
t1 : 100
可以看到虽然t1,t2两个线程先后修改了ThreadLocal变量,但是之后取出的话还是能够保证,取出的是自己设置的而不是其他线程设置的。
深入了解ThreadLocal
相比于普通变量ThreadLocal的确有一种谜一样的魅力,接下来就开始看一下,它是如何做到多个线程对同一个变量操作但是却不互相影响的呐?
set()方法
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//getMap方法,就一句话 return t.threadLocals 。
//返回了Thead中的一个成员变量。
//这个成员变量的类型是ThreadLocal里面定义的一个内部类ThreadLocalMap
//一个Map的结构
ThreadLocal.ThreadLocalMap map = getMap(t);
//如果存在的话,则往这个map里面set值
//其实就是想currentThread的一个ThreadLocalMap类型的Map里面设置值
//这个Map的key,就是当前这个ThreadLocal对象
if (map != null)
map.set(this, value);
else
//不存在,就会创建一个ThreadLocalMap,
//并以当前ThreadLocal对象为key,将k-v放入到ThreadLocalMap中
//最后把ThreadLocalMap,赋值给currentThread当前线程
createMap(t, value);
}
get()方法
public T get() {
//获取当前线程里面的成员变量ThreadLocalMap
Thread t = Thread.currentThread();
ThreadLocal.ThreadLocalMap map = getMap(t);
//当Map不为空的时候,尝试以当前的ThreadLocal对象为key
//从这个TreadLocalMap里面取出Entry对象
if (map != null) {
ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
//当Entry对象不为空,则强转后返回
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
这两个是ThreadLocal中最重要的方法,基本上已经揭开了ThreadLocal的面纱。ThreadLocal更像是一个“工具壳”。通过这个“工具壳”,你可以操作到当前Thread对象的成员变量threadLocals。这个成员变量的类型是ThreadLocal.ThreadLocalMap,他的key就是当前ThreadLocal对象,value就是你要设置的值。需要注意的是这些值本质存储在当前线程对象的一个Map结构中,如果线程不消亡,那么这个变量就会一直存在,所以可能会造成内存溢出。
//Thread类里面的定义的两个针对ThreadLocal实现的成员变量
public class Thread implements Runnable{
//普通的ThreadLocal对象重点操作的Map
ThreadLocal.ThreadLocalMap threadLocals = null;
//这个Map是为了让ThreadLocal支持继承
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
支持继承的ThreadLocal
ThreadLocal是不支持继承的。这里的继承意思是:在父线程内,通过ThreadLocal设置的值,在子线程内是拿不到的。
//创建一个ThreadLocal,并设值,main方法本身启动一个父线程
ThreadLocal threadLocal = new ThreadLocal<>();
threadLocal.set("mainThread");
new Thread(()->{
//这里子线程并不能拿到父线程的值
System.out.println(threadLocal.get());
},"subThread").start();
这就有了后面的支持继承的ThreadLocal - InheritableThreadLocal类
(注意:Thread类中两个成员变量,另一个就是为了支持这个特性的)。
//InheritableThreadLocal 类内容比较简单它继承并重写了ThreadLocal的3个方法
public class InheritableThreadLocal extends ThreadLocal {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
//上面ThreadLocal的源码,这个方法主要返回了
//操作当前线程的那个成员变量
//可以看出Inher..ThreaLocal主要操作的是下面这个
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
//同样的创建的时候也是哈给Thread对象的
//inheritableThreadLocals赋值
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
综合的来说,InheritableThreadLocal这个工具壳子,重点操作的是Thread对象的inheritableThreadLocals成员变量。但是据此并不能保证它继承的特性。嘿嘿嘿,重点在于Thread类的init方法(ThreadLocal和Thread,他俩是打配合的哈)。
//Thread类的构造函数会调用,init方法,所以重点就是init方法
//调用了几层才到最终的init方法的
public Thread(Runnable runnable){init(...)}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
Thread parent = currentThread();
...
//重点是这句话,当父线程不为空时,会将父线程中的值复制到子线程的属性中
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
//createInhertedMap直接调用了
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
//最终是在ThreadLocalMap的构造函数中,
//将父线程的inheritableThreadLocals,给copy到子线程的inheritableThreadLocals。
private ThreadLocalMap(ThreadLocalMap parentMap) {
//ThreadLocalMap 里面有个 Entry数组
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
//长度就是parentTable的length
//其实也跟hashmap的实现一致,也会保证是2的次方
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
//获取key,这个map的key是一个ThreadLocal对象
ThreadLocal
总的来说,对于InheritableThreadLocal,它重点操作了Thread对象的inheritableThreadLocals属性,当创建线程时,会判断父线程这个属性是否为空,不为空,则会把这个属性给复制到子线程中,这样子线程就可以访问父线程的本地变量了。
嘿嘿嘿,今天先到这里。