前言
参考资料:
《Java并发编程之美(1.11 ThreadLocal)》
快速上手
public class ThreadLocal1 {
private static final ThreadLocal<String> USER = new ThreadLocal<>();
static Thread t1 = new Thread(() -> {
ThreadLocal1.common();
});
static Thread t2 = new Thread(() -> {
ThreadLocal1.common();
});
public static void common() {
Thread thread = Thread.currentThread();
USER.set(thread.getName());
System.out.println(thread.getId() + ";" + thread.getName() + ";" + USER.get());
USER.remove();
}
public static void main(String[] args) throws Exception {
ThreadLocal1.common();
t1.start();
t2.start();
t1.join();
t2.join();
}
}
1;main;main
20;Thread-0;Thread-0
21;Thread-1;Thread-1
ThreadLocal
package java.lang;
public class ThreadLocal<T> {}
lang包下的类,无额外继承和实现。
public方法
构造函数
public ThreadLocal() {
}
get
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();
}
remove
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
调用ThreadLocalMap中的remove方法。
set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
withInitial
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
内部类
SuppliedThreadLocal
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
ThreadLocalMap(重要)
static class ThreadLocalMap {}
Entry
Entry是ThreadLocalMap中的静态内部类,继承WeakReference弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
继承关系
Reference
InheritableThreadLocal(itl、ITL)
Inherit:继承
快速上手
获取父线程的值,我们使用一个案例来比较tl和itl的区别。
public class FromParent {
static final ThreadLocal<String> tl = new ThreadLocal<>();
static final ThreadLocal<String> itl = new InheritableThreadLocal<>();
public static void main(String[] args) {
// FromParent.m1();
FromParent.m2();
}
static void m1() {
FromParent.common(tl);
}
static void m2() {
FromParent.common(itl);
}
static void common(ThreadLocal<String> tl) {
tl.set(Thread.currentThread().getName());
Thread t1 = new Thread(() -> {
System.out.println("t1:" + tl.get());
});
t1.start();
}
}
t1:null
t1:main
执行m1输出null,执行m2输出main。
源码
package java.lang;
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);
}
}
由于没有新增方法,所以使用起来跟ThreadLocal一样,仅仅重写和实现了部分方法。
区别:由操作threadLocals变为了inheritableThreadLocals
thread中部分变量
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
为什么InheritableThreadLocal可以获取到父线程的值
在快速入手中我们演示了一个案例,那么为什么可以获取到呢?当我们new一个线程的时候
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);
}
Thread类init方法部分代码如下,根据上面最后一个参数inheritThreadLocals为true,第一个条件满足。
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
当我们调用tl.set的时候会调用itl中的createMap创建inheritableThreadLocals,第二个条件满足。
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
最终结果新创建的线程t1调用createInheritedMap
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
InheritableThreadLocal存在的问题
上下文异步线程池问题:我们使用ITL存储用户信息上下文时,使用异步任务从线程池中尝试获取ITL,结果是无法获取到正确的用户信息。下面我们来看一个案例
public class InheritableThreadLocalProblem {
static final ThreadLocal<String> itl = new InheritableThreadLocal<>();
static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(1);
public static void main(String[] args) throws Exception {
InheritableThreadLocalProblem.common();
}
static void common() throws Exception {
for (int i = 1; i <= 5; i++) {
itl.set(String.valueOf(i));
System.out.println(Thread.currentThread().getName() + ":" + itl.get());
Runnable r = () -> System.out.println(Thread.currentThread().getName() + ":" + itl.get());
EXECUTOR.execute(r);
TimeUnit.MILLISECONDS.sleep(500);
System.out.println("-----" + i + "----");
}
EXECUTOR.shutdown();
}
}
main:1
pool-1-thread-1:1
-----1----
main:2
pool-1-thread-1:1
-----2----
main:3
pool-1-thread-1:1
-----3----
main:4
pool-1-thread-1:1
-----4----
main:5
pool-1-thread-1:1
-----5----
当线程池里面的线程第一次执行初始化(init)后,后续父ITL变化子线程不会响应。下面我们来看两种解决方案。
方法一:TransmittableThreadLocal(TTL)
第一步:添加依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.12.6</version>
</dependency>
第二步:实战
static final ThreadLocal<String> ttl = new TransmittableThreadLocal<>();
static void p2() throws Exception {
for (int i = 1; i <= 5; i++) {
ttl.set(String.valueOf(i));
System.out.println(Thread.currentThread().getName() + ":" + ttl.get());
Runnable r = () -> System.out.println(Thread.currentThread().getName() + ":" + ttl.get());
TtlRunnable tr = TtlRunnable.get(r);
EXECUTOR.execute(tr);
TimeUnit.MILLISECONDS.sleep(500);
System.out.println("-----" + i + "----");
}
EXECUTOR.shutdown();
}
第一步引入TTL,第二步使用TtlRunnable.get包装Runnable接口。
main:1
pool-1-thread-1:1
-----1----
main:2
pool-1-thread-1:2
-----2----
main:3
pool-1-thread-1:3
-----3----
main:4
pool-1-thread-1:4
-----4----
main:5
pool-1-thread-1:5
-----5----
方法二:获取转变为传参
static void p1() throws Exception {
for (int i = 1; i <= 5; i++) {
itl.set(String.valueOf(i));
final int val = i;
System.out.println(Thread.currentThread().getName() + ":" + itl.get());
Runnable r = () -> System.out.println(Thread.currentThread().getName() + ":" + val);
EXECUTOR.execute(r);
TimeUnit.MILLISECONDS.sleep(500);
System.out.println("-----" + i + "----");
}
EXECUTOR.shutdown();
}
main:1
pool-1-thread-1:1
-----1----
main:2
pool-1-thread-1:2
-----2----
main:3
pool-1-thread-1:3
-----3----
main:4
pool-1-thread-1:4
-----4----
main:5
pool-1-thread-1:5
-----5----
本来我们会在子线程中获取上下文信息,我们修改为子线程业务参数是上下文信息,也就是将用户信息作为参数传入到Runnable 接口。