ThreadLocal在多个线程中针对一个线程维护不同值的功能,这样我们就不需要在每个线程内都传这个参数。ThreadLocal的子类InheritableThreadLocal在ThreadLocal的基础上,解决了父子线程值传递的问题。但是在线程池中由于线程的复用,线程在创建的时候会把父线程当前的inheritableThreadLocals拷贝过去,之后我们再在代码中设置了InheritableThreadLocal后,在线程池中的线程就再也获取不到这个新的InheritableThreadLocal,导致数据错乱,阿里开源的TransmittableThreadLocal框架就是解决这个问题。
首先看个ThreadLocal的小例子
package com.spring.learn.threadlocal;
/**
* @author : caoyu create at: 2019-11-07
* @description:
*/
public class ThreadLocalTest {
public static void main(String s[]) {
ThreadLocal threadLocal1 = new ThreadLocal();
threadLocal1.set("threadLocal1");
new Thread(new Runnable() {
@Override
public void run() {
ThreadLocal threadLocal2 = new ThreadLocal();
System.out.println(Thread.currentThread().getName() + ":" + threadLocal1.get());
threadLocal2.set("threadLocal2");
System.out.println(Thread.currentThread().getName() + ":" + threadLocal2.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
ThreadLocal threadLocal3 = new ThreadLocal();
System.out.println(Thread.currentThread().getName() + ":" + threadLocal1.get());
threadLocal3.set("threadLocal3");
System.out.println(Thread.currentThread().getName() + ":" + threadLocal3.get());
}
}).start();
System.out.println(Thread.currentThread().getName() + ":" + threadLocal1.get());
}
}
运行结果为:
Thread-0:null
Thread-0:threadLocal2
Thread-1:null
Thread-1:threadLocal3
main:threadLocal1
我们看到不同线程间threadLocal的值是不一样的,那是如何做到的呢?
ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值,ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal的set方法会根据当前线程返回Thread的变量ThreadLocal.ThreadLocalMap threadLocals,如果为空会新建一个ThreadLocalMap,并将值赋值给当前线程的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);
//遍历tab如果已经存在则更新值
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();
}
public class ThreadLocal<T> {
/**
* ThreadLocals rely on per-thread linear-probe hash maps attached
* to each thread (Thread.threadLocals and
* inheritableThreadLocals). The ThreadLocal objects act as keys,
* searched via threadLocalHashCode. This is a custom hash code
* (useful only within ThreadLocalMaps) that eliminates collisions
* in the common case where consecutively constructed ThreadLocals
* are used by the same threads, while remaining well-behaved in
* less common cases.
*/
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out. Updated atomically. Starts at
* zero.
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
因为static的原因,在每次new ThreadLocal时因为threadLocalHashCode的初始化,会使threadLocalHashCode值自增一次,增量为0x61c88647。
由此可以看出每个ThreadLocal的索引值都是不同的,根据ThreadLocal的索引下标值构建Entry并存放至Entry[]数组中。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
理解ThreadLocal的set方法再去看get方法就比较容易了,从上面的源码中可以看到根据当前线程获取线程的threadLocals变量,根据ThreadLocal获取数组下标再从ThreadLocalMap取出值返回。如果线程threadLocals变量为空,则会调用setInitialValue方法。
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
package com.spring.learn.threadlocal;
import java.util.concurrent.CompletableFuture;
/**
* @author : caoyu create at: 2019-11-11
* @description:
*/
public class InheritableThreadLocalTest {
public static void main(String s[]) {
InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal();
User user = new User();
user.setName(Thread.currentThread().getName());
inheritableThreadLocal.set(user);
System.out.println(Thread.currentThread().getName() + ":" + user);
CompletableFuture.allOf(CompletableFuture.runAsync(() -> {
User user1 = (User) inheritableThreadLocal.get();
System.out.println(Thread.currentThread().getName() + ":" + user1);
user1.setName(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName() + ":" + user1);
})).join();
System.out.println(Thread.currentThread().getName() + ":" + inheritableThreadLocal.get());
}
public static class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" + "name='" + name + '\'' + '}';
}
}
}
运行结果:
main:User{name='main'}
ForkJoinPool.commonPool-worker-1:User{name='main'}
ForkJoinPool.commonPool-worker-1:User{name='ForkJoinPool.commonPool-worker-1'}
main:User{name='ForkJoinPool.commonPool-worker-1'}
我们看到main线程设置的值传递到Thread-0线程中。
package java.lang;
import java.lang.ref.*;
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
//这个方法留给子类实现
protected T childValue(T parentValue) {
return parentValue;
}
//重写getMap方法,返回的是Thread的inheritableThreadLocals引用
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
//重写createMap方法,构造的ThreadLocalMap会传给Thread的inheritableThreadLocals 变量
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
从源码上看,跟ThreadLocal不一样的无非是重写了getMap、createMap两个方法,ThreadLocalMap的引用不一样了,从逻辑上来讲,这并不能做到子线程得到父线程里的值。那秘密在那里呢?我们需要看一下Thread构造方法。
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
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();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
//判断父线程的inheritableThreadLocals变量是否为空,不为空将值传递给子线程的inheritableThreadLocals变量
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
通过跟踪Thread的构造方法,我们发现只要父线程在构造子线程(调用new Thread())的时候inheritableThreadLocals变量不为空。新生成的子线程会通过ThreadLocal.createInheritedMap方法将父线程inheritableThreadLocals变量有的对象复制到子线程的inheritableThreadLocals变量上。这样就完成了线程间变量的继承与传递。
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
//这里是复制parentMap数据的逻辑
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) {
//重写InheritableThreadLocal的childValue方法
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++;
}
}
}
}
子线程通过继承得到的InheritableThreadLocal里的值与父线程里的InheritableThreadLocal的值具有相同的引用,如果父子线程想实现不影响各自的对象,可以重写InheritableThreadLocal的childValue方法。
InheritableThreadLocal可以解决在多线程中父子线程值的传递,但是在线程池中由于线程的复用InheritableThreadLocal就不在适用。引入阿里开源的TransmittableThreadLocal解决在线程池中值的传递。
package com.spring.learn.threadlocal;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* @author : caoyu create at: 2019-11-11
* @description:
*/
public class TransmittableThreadLocalTest {
public static void main(String s[]) {
InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal();
Executor executor1 = Executors.newFixedThreadPool(1);
for (int i = 0; i < 3; i++) {
User user = new User();
user.setName(Thread.currentThread().getName() + i);
inheritableThreadLocal.set(user);
CompletableFuture.allOf(CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + ":" + inheritableThreadLocal.get());
}, executor1)).join();
}
TransmittableThreadLocal transmittableThreadLocal = new TransmittableThreadLocal();
Executor executor2 = TtlExecutors.getTtlExecutor(Executors.newFixedThreadPool(1));
for (int i = 0; i < 3; i++) {
User user = new User();
user.setName(Thread.currentThread().getName() + i);
transmittableThreadLocal.set(user);
CompletableFuture.allOf(CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + ":" + transmittableThreadLocal.get());
}, executor2)).join();
}
}
public static class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" + "name='" + name + '\'' + '}';
}
}
}
运行结果:
pool-1-thread-1:User{name='main0'}
pool-1-thread-1:User{name='main0'}
pool-1-thread-1:User{name='main0'}
pool-2-thread-1:User{name='main0'}
pool-2-thread-1:User{name='main1'}
pool-2-thread-1:User{name='main2'}
可以发现线程池pool-1中使用InheritableThreadLocal传递参数,结果都是一样,可以发现线程池pool-2中使用TransmittableThreadLocal传递参数,参数传递符合预期。