ThreadLocal
ThreadLocal是Thread的局部变量,可以理解为ThreadLocalVariable。它在ThreadLocal类中定义了一个ThreadLocalMap,每一个Thread中都有一个该类型的变量——threadLocals——用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。
ThreadLocal在不同线程之间值传递
ThreadLocal与当前线程绑定的变量赋值时,那切换到其他线程则获取不到该值,那么下面看看有没有什么解决办法。
现象 | 解决方案 | 描述 |
同一线程之间传递值 | ThreadLocal | 给线程提供一个本地变量,当线程消失的时候,所有的本地示例都会被回收 |
父子线程之间传递值 | InheritableThreadLocal | jdk提供的类,是ThreadLocal的升级版,解决父子线程之间传递值的问题 |
线程池不同线程传递值 | TransmittableThreadLocal | 简称TTL,是阿里巴巴团队提供的一个框架,主要解决因为存储线程池InheritableThreadLocal失效的问题 |
ThreadLocal示例
示例一 ThreadLocal线程隔离
ThreadLocal是线程的本地变量,下面一个示例是启了十个线程,每个线程都有一个ThreadLocal,每个线程里初始化值都是100,然后每个线程中去改变变量值+1,最后结果可以看出每个线程都是互不影响的。
public class ThreadLocalMapDemo extends Thread {
/**
* ThreadLocal特性:
* 1、线程并发:在多线程并发场景下使用。
* 2、传递数据:可以通过ThreadLocal在同一线程,不同组件中传递公共变量。
* 3、线程隔离:每个线程变量都是独立的,不会相互影响。
*
* ThreadLocalMap是ThreadLocal的静态内部类
* 与HashMap类似,初始容量默认是16,初始容量必须是2的整数幂。通过Entry类的数据table存放数据。size是存放的数量,threshold是扩容阈值。
* Entry继承自WeakReference,key是弱引用,其目的是将ThreadLocal对象的生命周期和线程生命周期解绑。
* 弱引用:垃圾回收器一旦发现了弱引用的对象,不管内存是否足够,都会回收它的内存。
*/
private static final ThreadLocal<Integer> THREAD_LOCAL = new ThreadLocal<>();
private Integer value;
ThreadLocalMapDemo(Integer value) {
this.value = value;
}
@Override
public void run() {
// 设置值不会影响其他线程
THREAD_LOCAL.set(value);
System.out.println("Thread name = " + currentThread().getName() + " current value = " + THREAD_LOCAL.get());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 重新设置值
THREAD_LOCAL.set(THREAD_LOCAL.get() + 1);
System.out.println("Thread name = " + currentThread().getName() + " current value after change is = " + THREAD_LOCAL.get());
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
ThreadLocalMapDemo demo = new ThreadLocalMapDemo(100);
demo.setName("线程" + i);
demo.start();
}
}
}
示例二
首先,定义一个ThreadLocal工具类,
/**
* ThreadLocal工具类
*
* @param <T> 范型T
*/
public final class ThreadLocalUtil<T> {
private final ThreadLocal<T> THREAD_LOCAL = new ThreadLocal<>();
/**
* 无参构造函数
*/
public ThreadLocalUtil() {
}
/**
* 有参构造函数
*
* @param t
*/
ThreadLocalUtil(T t) {
THREAD_LOCAL.set(t);
}
/**
* 从ThreadLocalMap中获取当前线程变量
*
* @return 变量值
*/
public T getValue() {
return THREAD_LOCAL.get();
}
/**
* ThreadLocalMap中写入当前线程变量
*
* @param t 入参
* @return 当前写入值
*/
public T setValue(T t) {
THREAD_LOCAL.set(t);
return t;
}
/**
* 从ThreadLocalMap中删除当前线程变量
*/
public void removeValue() {
THREAD_LOCAL.remove();
}
}
主线程中定义一个静态变量util,初始值是123,在子线程和主线程中分别打印ThreadLocal变量值。
public class ThreadLocalExample {
private static Logger logger = LoggerFactory.getLogger(ThreadLocalExample.class);
private static ThreadLocalUtil<Integer> util = new ThreadLocalUtil<>(123);
public static void main(String[] args) {
new Thread(() -> logger.info("Thread name = {}, thread variable is {}", Thread.currentThread().getName(), util.getValue())).start();
logger.info("Thread name = {}, thread variable is {}", Thread.currentThread().getName(), util.getValue());
}
}
示例结果如下,子线程输出的是子线程初始值null,主线程输出的是初始值123。另外,这里不保证线程的有序性,只是简易示例。
InheritableThreadLocal(ITL)示例
InheritableThreadLocal主要用于子线程创建时,需要自动继承父线程的ThreadLocal变量时使用。
把上面的代码稍微改造一下,
/**
* ThreadLocal工具类
*
* @param <T> 范型T
*/
public final class ThreadLocalUtil<T> {
private final ThreadLocal<T> THREAD_LOCAL = new InheritableThreadLocal<>();
/**
* 无参构造函数
*/
public ThreadLocalUtil() {
}
/**
* 有参构造函数
*
* @param t
*/
ThreadLocalUtil(T t) {
THREAD_LOCAL.set(t);
}
/**
* 从ThreadLocalMap中获取当前线程变量
*
* @return 变量值
*/
public T getValue() {
return THREAD_LOCAL.get();
}
/**
* ThreadLocalMap中写入当前线程变量
*
* @param t 入参
* @return 当前写入值
*/
public T setValue(T t) {
THREAD_LOCAL.set(t);
return t;
}
/**
* 从ThreadLocalMap中删除当前线程变量
*/
public void removeValue() {
THREAD_LOCAL.remove();
}
}
代码不变,子线程中输出的ThreadLocal变量继承了主线程中的ThreadLocal变量值,
TransmittableThreadLocal(TTL)示例
TransmittableThreadLocal,简称TTL,它不同于ITL,它能实现池化线程间传递。直接看官方的时序图,
大部分过程都依赖于 TransmittableThreadLocal 或 TransmittableThreadLocal 中声明的静态工具类 Transmitter 。Transmitter 主要负责 ThreadLocal 的管理和值的传递。
使用前需要导入依赖,
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.12.6</version>
</dependency>
下面启了两个线程main_01和main_02作为父线程,线程池中执行的三个子线程拿到父线程设置的ttl变量,父线程再修改ttl值,然后线程池中的子线程继续读取ttl中的值。
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TransmittableThreadLocalDemo {
/**
* 需要注意的是,使用TTL的时候,要想传递的值不出问题,线程池必须得用TTL加一层代理
*/
private static ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
private static ThreadLocal<Integer> ttl = new TransmittableThreadLocal<>();
public static void main(String[] args) {
new Thread(() -> {
String mainThreadName = "main_01";
ttl.set(1);
executorService.execute(() -> {
sleep(1L);
System.out.println(String.format("本地变量改变之前(1), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
});
executorService.execute(() -> {
sleep(1L);
System.out.println(String.format("本地变量改变之前(1), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
});
executorService.execute(() -> {
sleep(1L);
System.out.println(String.format("本地变量改变之前(1), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
});
sleep(1L); //确保上面的会在tl.set执行之前执行
ttl.set(2); // 等上面的线程池第一次启用完了,父线程再给自己赋值
executorService.execute(() -> {
sleep(1L);
System.out.println(String.format("本地变量改变之后(2), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
});
executorService.execute(() -> {
sleep(1L);
System.out.println(String.format("本地变量改变之后(2), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
});
executorService.execute(() -> {
sleep(1L);
System.out.println(String.format("本地变量改变之后(2), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
});
System.out.println(String.format("线程名称-%s, 变量值=%s", Thread.currentThread().getName(), ttl.get()));
}).start();
new Thread(() -> {
String mainThreadName = "main_02";
ttl.set(3);
executorService.execute(() -> {
sleep(1L);
System.out.println(String.format("本地变量改变之前(3), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
});
executorService.execute(() -> {
sleep(1L);
System.out.println(String.format("本地变量改变之前(3), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
});
executorService.execute(() -> {
sleep(1L);
System.out.println(String.format("本地变量改变之前(3), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
});
sleep(1L); //确保上面的会在tl.set执行之前执行
ttl.set(4); // 等上面的线程池第一次启用完了,父线程再给自己赋值
executorService.execute(() -> {
sleep(1L);
System.out.println(String.format("本地变量改变之后(4), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
});
executorService.execute(() -> {
sleep(1L);
System.out.println(String.format("本地变量改变之后(4), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
});
executorService.execute(() -> {
sleep(1L);
System.out.println(String.format("本地变量改变之后(4), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
});
System.out.println(String.format("线程名称-%s, 变量值=%s", Thread.currentThread().getName(), ttl.get()));
}).start();
}
private static void sleep(long longTime) {
try {
Thread.sleep(longTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
最终结果如下,线程池中子线程拿到的是父线程中设置的值。
总结
ThreadLocal 的使用,本身类似于全局变量,而且是可修改的。一旦中间过程被修改,就无法保证整体流程的前后一致性。
应该尽量避免在业务代码中使用的。
Do not use, only when you know why.