一、ThreadLocal
ThreadLocal是JDK自带的工具类,主要是为了我们方便我们在同一个线程中进行变量的共享。
下面是基于ThreadLocal的代码演示
public class ThreadLocalUtils {
public static ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
}
public class ThreadLocalGetTest {
public String testGet(){
return ThreadLocalUtils.THREAD_LOCAL.get();
}
}
public class ThreadLocalTest {
static ExecutorService executorService = Executors.newFixedThreadPool(1);
public static void main(String[] args) throws InterruptedException {
ThreadLocalUtils.THREAD_LOCAL.set("woaiwojia11111");
ThreadLocalGetTest threadLocalGetTest1 = new ThreadLocalGetTest();
System.out.println("step1:" + threadLocalGetTest1.testGet());
executorService.execute(() -> {
System.out.println("step2:" + threadLocalGetTest1.testGet());
ThreadLocalUtils.THREAD_LOCAL.set("woaiwojia22222");
ThreadLocalGetTest threadLocalGetTest2 = new ThreadLocalGetTest();
System.out.println("step3:" + threadLocalGetTest1.testGet());
System.out.println("step4:" + threadLocalGetTest2.testGet());
});
Thread.sleep(1000);
System.out.println("step5:" + threadLocalGetTest1.testGet());
}
}
输出结果
step1:woaiwojia11111
step2:null
step3:woaiwojia22222
step4:woaiwojia22222
step5:woaiwojia11111
如果对ThreadLocal不了解的同学,是不是觉得这个结果有点意外
在看ThreadLocal源码之前,我们先来看ThreadLocalMap,他是ThreadLocal的一个静态内部类,里面使用一个Entry来存储数据
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
他是一个key value结构的数据,其中key为我的threadLocal,value为我们设置的值
类似下面
key | value |
threadLocal1 | value1 |
threadLocal2 | value2 |
接下来我们来看看ThreadLocal的源码,首先看java.lang.ThreadLocal#set(T value)方法
public void set(T value) {
//首先在这里获取当前的执行线程
Thread t = Thread.currentThread();
//获取当前线程中的ThreadLocalMap(下面会讲解)
ThreadLocalMap map = getMap(t);
if (map != null)
//如果存在,则直接将值设置到ThreadLocalMap中
map.set(this, value);
else
//如果不存在,则新创建一个map
createMap(t, value);
}
//这个就是获取当前线程的threadLocals,他是ThreadLocalMap类型的变量
ThreadLocalMap getMap(Thread t) {
// ThreadLocal.ThreadLocalMap threadLocals = null;
return t.threadLocals;
}
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
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;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
//创建一个新的
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//这里就是简单的一个创建ThreadLocalMap,并设置一些基础的属性
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
//设置阈值,类似hashMap的threshold变量,里面的实现为threshold = len * 2 / 3;
setThreshold(INITIAL_CAPACITY);
}
步骤解析:
set设置值
1.获取当前线程,从当前线程中获取当前线程的ThreadLocalMap变量(这个变量是每个线程私有的变量,线程之间不共享)
2.如果threadLocals这个变量存在,则将当前需要设置的变量设置到threadLocals这个变量中,其中key为当前的threadLocal,value为设置的值(这块代码不难理解)
3.如果不存在,则根据当前的threadLocal和value值创建一个ThreadLocalMap,并设置到当前线程的threadLocals中。(因为这个变量是私有的,所以不用担心重复创建,导致数据丢失)
get获取值
当threadLocal首次调用get获取值的时候,返回为一个null值,并且会初始化这个threadLocals这个变量。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//如果当前线程的threadLocals不为null,则直接从threadLocals获取值,如果为null则调用setInitialValue()
//根据当前的threadLocal从threadLocals中获取值,如果获取到值,直接返回值
//如果为空,则调用setInitialValue()
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private T setInitialValue() {
//这个value为一个null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
二、InheritableThreadLocal
这个也是JDK中自带的工具类,从名字上可以知道,这个是可继承的ThreadLocal
public class InheritableThreadLocalTest {
static ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
static ExecutorService executorService = Executors.newFixedThreadPool(1);
public static void main(String[] args) throws InterruptedException {
inheritableThreadLocal.set("woaiwojia00000");
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("step1:" + inheritableThreadLocal.get());
inheritableThreadLocal.set("woaiwojia11111");
}
});
TimeUnit.SECONDS.sleep(1);
System.out.println("step2:" + inheritableThreadLocal.get());
inheritableThreadLocal.set("woaiwojia22222");// 主线程设置新的值
System.out.println("step3:" + inheritableThreadLocal.get());
TimeUnit.SECONDS.sleep(1);
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("step4:" + inheritableThreadLocal.get());
}
});
TimeUnit.SECONDS.sleep(1);
System.out.println("step5:" + inheritableThreadLocal.get());
}
}
运行结果
step1:woaiwojia00000
step2:woaiwojia00000
step3:woaiwojia22222
step4:woaiwojia11111
step5:woaiwojia22222
从运行结果来看
step1输出的结果为主线程设置的值
step2输出的结果为主线程设置的值
step3为主线程修改的值
step4为线程池里线程自己设置的值,这里并没有将主线程的值复制过来,也不会把值传递到主线程中,从step2和step5输出的结果也可以看出来
step5为主线程修改的值
接下来,我们从源码角度分析InheritableThreadLocal
首先看看InheritableThreadLocal中的代码
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
InheritableThreadLocal重写了ThreadLocal中的三个方法childValue(T parentValue ),getMap(Thread t),createMap(Thread t, T firstValue),其他的方法都和ThreadLocal一样。
从上面的InheritableThreadLocal的源码可以看出,InheritableThreadLocal的getMap获取的不是threadLocals这个变量,而是inheritableThreadLocals。那是什么时候把主线程中的值复制给子线程中的呢,答案是这个过程发生在创建子线程的时候。
下面我们看看创建线程的源码
Thread.java
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
//获取当前线程,作为子线程的父线程
Thread parent = currentThread();
......
//如果当前线程中的inheritThreadLocals不为null,则将inheritThreadLocals复制到当前正在创建的线程的inheritThreadLocals
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
......
}
ThreadLocal.java
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
通过上面的代码我们了解,在使用线程池的情况下,只有在第一次创建线程的时候,才会将主线程中的变量copy到子线程中,那我们在平时的代码中,通常都是复用之前创建的线程。那我们想一直使用主线程设置的值该怎么办呢。这时候,我们的TransmittableThreadLocal可以派上用场了。
三、TransmittableThreadLocal
TransmittableThreadLocal不是JDK提供的工具类,而是阿里的一个开源工具类,从他所在的包名就可以看出来com.alibaba.ttl
使用这个工具类我们需要引入包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.10.2</version>
</dependency>
TransmittableThreadLocal是InhritableThreadLocal的子类
下面看演示代码
public class TransmittableThreadLocalTest {
static ThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
//创建一个Ttl线程池
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService = TtlExecutors.getTtlExecutorService(executorService);
threadLocal.set("woaiwojia11111");
Thread.sleep(1000L);
executorService.execute(() -> {
System.out.println("step1:" + threadLocal.get());
threadLocal.set("woaiwojia22222");
});
Thread.sleep(1000L);
executorService.execute(() -> {
System.out.println("step2:" + threadLocal.get());
});
threadLocal.set("woaiwojia33333");
executorService.execute(() -> {
System.out.println("step3:" + threadLocal.get());
});
Thread.sleep(1000L);
System.out.println("step4:" + threadLocal.get());
}
}
输出结果
step1:woaiwojia11111
step2:woaiwojia11111
step3:woaiwojia33333
step4:woaiwojia33333
从输出结果来看,即使我们在step1处设置了子线程的值,但是再次调用的时候,还是主线程传入的值。
下面我们从源码分析TransmisttableThreadLocal是如何实现线程间传递,以及不仅仅是在第一次创建线程的时候将主线程的值传递到子线程中。
TransmisttableThreadLocal覆盖了ThreadLocal的set,get,remove方法,实际存储ThreadLocal值的还是他的父类完成的。而TransmisttableThreadLocal只是为每个使用他的Thread记录一份存储哪些TransmisttableThreadLocal对象。
拿set来说
@Override
public final void set(T value) {
super.set(value);
// may set null to remove value
if (null == value) removeValue();
else addValue();
}
private void addValue() {
if (!holder.get().containsKey(this)) {
holder.get().put(this, null); // WeakHashMap supports null value.
}
}
private static InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder =
new InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() {
@Override
protected Map<TransmittableThreadLocal<?>, ?> initialValue() {
return new WeakHashMap<TransmittableThreadLocal<?>, Object>();
}
@Override
protected Map<TransmittableThreadLocal<?>, ?> childValue(Map<TransmittableThreadLocal<?>, ?> parentValue) {
return new WeakHashMap<TransmittableThreadLocal<?>, Object>(parentValue);
}
};
我们在使用TtlExecutorService提交线程时,会将Runable封装为TtlRunnable,在转换的过程中,会将TransmittableThreadLocal.holder中的ThreadLocal复制到capturedRef属性中,在子线程调用run方法的时候,会将capturedRef复制到子线程的ThreadLocal中。
@Override
public void run() {
Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
Object backup = replay(captured);
try {
runnable.run();
} finally {
restore(backup);
}
}