ThreadLocal介绍
一、ThreadLocal 概述
众所周知,ThreadLocal不存在线程安全问题,那是因为ThreadLocal维护变量的时候,它为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个该变量,这样同时多个线程访问该变量并不会彼此相互影响,因此他们使用的都是自己从内存中拷贝过来的变量的副本。
Thread 在内部是通过ThreadLocalMap来维护ThreadLocal变量表, 在Thread类中有一个threadLocals 变量,是ThreadLocalMap类型的,它就是为每一个线程来存储自身的ThreadLocal变量的, ThreadLocalMap是ThreadLocal类的一个内部类,这个Map里面的最小的存储单位是一个Entry, 它使用ThreadLocal作为key, 变量作为 value,这是因为在每一个线程里面,可能存在着多个ThreadLocal变量。
1.1 源码剖析
讲解主要代码过程
1.1.1 set(T value)
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//根据当前线程获取线程变量,threadlocal的是threadLocals变量
ThreadLocalMap map = getMap(t);
//为空创建map,不为空设置值
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
1.1.2 get()
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//根据当前线程获取线程变量,threadlocal的是threadLocals变量
ThreadLocalMap map = getMap(t);
//如果当前map不为空的情况
if (map != null) {
//获取当前线程的节点数据并且返回
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果map为空的情况下,初始化这个线程的map,value设为null值并返回
return setInitialValue();
}
1.1.3 remove()
public void remove() {
//获取map,移除变量
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
1.2 ThreadLocalMap
ThreadLocalMap不是普通的Map而是一个定制Map,这个Map使用了使用“开放寻址法”中最简单的“线性探测法”解决散列冲突问题。这点是和我们平常使用的普通的HashMap不一样的。其中还是用了一个神奇的数字HASH_INCREMENT= 0x61c88647,保证了一个完美的散列分布。
1.4 优点
由于每个线程里面的变量都是隔离的,避免了线程安全的问题。
1.5 缺点
父子线程中就有了局限性,如果子线程拿不到父线程的中的ThreadLocal值。
二、InheritableThreadLocal(ITL)
ThreadLocal由于父子线程之间存在局限性,JD提供了实现方案:InheritableThreadLocal,这里需要注意的是InheritableThreadLocal源码并没有提供太多的方法,只是重写了三个方法。原本ThreadLocal存在Thread里的变量为threadLocals改成了使用inheritableThreadLocals。那么只重写了几个方法的情况下如何实现父线程向子线程传递呢。重点内容在这里:
Thread类的构造方法:
//当前线程不为空并且当前线程的inheritableThreadLocals不为空的情况下,将当前线程的inheritableThreadLocals传递到new的线程里,这里就实现了变量复制
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
InheritableThreadLocal对于整个ThreadLocal的操作是不变的。
2.1 优点
InheritableThreadLocal优点在于父线程向子线程传递了属于自己的线程的变量
2.2 缺点
InheritableThreadLocal只能对通过手动新建的线程有效,对于当前使用火热的线程池是无效的。
三、TransmittableThreadLocal(TTL)
首先介绍以下这个类不是jdk自带的类,是阿里的库,其次TransmittableThreadLocal是为了解决InheritableThreadLocal对于线程池无效的情况而产生的。
我们在使用线程的时候往往不会只是简单的new Thrad对象,而是使用线程池。TTL的做法也比较直接,使用了装饰器模式,既然InheritableThreadLocal只是在线程Create的时候复制一份父线程数据,那么为了支持线程池就需要在Thread的run方法之前把父线程的数据copy一下就可以了。
ttl如何保证在线程池里实现父子线程之间的共享:
继承Runable,重写runable方法,在执行run方法之前copy一份线程变量
public final class TtlRunnable implements Runnable {
private final AtomicReference<Map<TransmittableThreadLocal<?>, Object>> copiedRef;
private final Runnable runnable;
private final boolean releaseTtlValueReferenceAfterRun;
private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
//从父类copy值到本类当中
this.copiedRef = new AtomicReference<Map<TransmittableThreadLocal<?>, Object>>(TransmittableThreadLocal.copy());
this.runnable = runnable;//提交的runable,被修饰对象
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
/**
* wrap method {@link Runnable#run()}.
*/
@Override
public void run() {
Map<TransmittableThreadLocal<?>, Object> copied = copiedRef.get();
if (copied == null || releaseTtlValueReferenceAfterRun && !copiedRef.compareAndSet(copied, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
//装载到当前线程
Map<TransmittableThreadLocal<?>, Object> backup = TransmittableThreadLocal.backupAndSetToCopied(copied);
try {
runnable.run();//执行提交的task
} finally {
//clear
TransmittableThreadLocal.restoreBackup(backup);
}
}
}
这是官方的流程图