1.概述
ThreadLocal将存储的数据和线程关联起来,来解决并发情况下数据一致性问题, 本文将介绍ThreadLocal的使用,结构和原理。
2.ThreadLocal 介绍
该类提供了线程局部变量,访问的是线程都有自己的、独立初始化的变量副本。ThreadLocal通常将一个类的私有静态变量状态和线程关联起来。每个线程都持有对其线程局部变量副本的隐式引用, 只要线程处于活动状态并且可以访问 ThreadLocal 实例; 线程消失后,它的所有线程本地实例副本都将进行垃圾回收(除非存在对这些副本的其他引用)
public class ThreadLocalDemo {
private static final AtomicInteger nextId = new AtomicInteger(0);
private static final ThreadLocal<Integer> threadId = new ThreadLocal<>();
public static int get() {
return threadId.get();
}
public static void set() {
threadId.set(nextId.incrementAndGet());
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(() -> {
ThreadLocalDemo.set();
System.out.println("Thread name=>"+Thread.currentThread().getName()+":"+ThreadLocalDemo.threadId.get());
},"Thread"+i);
thread.start();
}
Thread.sleep(2000);
System.out.println("Thread name"+Thread.currentThread().getName()+":"+ ThreadLocalDemo.nextId);
}
}
//output
Thread name=>Thread0:1
Thread name=>Thread2:4
Thread name=>Thread3:5
Thread name=>Thread4:3
Thread name=>Thread1:2
Thread namemain:5
如上程序类类的有一个ThreadLocal类型的成员变量threadId,启动线程将值加一,每个线程自己持有的threadId都不一样。而nextId是线程共享的。
3.
源码分析
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
//设置值的时候,先判断map有没有创建,如果没有创建则创建,如果已经创建,则将线程和要设置的值关联起来
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;//返回线程关联的ThreadLocal.ThreadLocalMap threadLocals
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);//创建ThreadLocalMap并且设置初始化值
}
//get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//根据key拿到ThreadLocalMap.Entry
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
//Entry 结构
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
4.线程池中使用ThreadLocal
public class ThreadLocalDemo implements Runnable{
private static final AtomicInteger nextId = new AtomicInteger(0);
private static final ThreadLocal<Integer> threadId = new ThreadLocal<>();
public static int get() {
return threadId.get();
}
public static void set() {
threadId.set(nextId.incrementAndGet());
}
@Override
public void run() {
ThreadLocalDemo.set();
System.out.println("Thread name=>" + Thread.currentThread().getName() + ":" + ThreadLocalDemo.threadId.get());
}
public static void main(String[] args) throws InterruptedException {
/* for (int i = 0; i < 5; i++) {
Thread thread = new Thread(() -> {
ThreadLocalDemo.set();
System.out.println("Thread name=>"+Thread.currentThread().getName()+":"+ThreadLocalDemo.threadId.get());
},"Thread"+i);
thread.start();
}
Thread.sleep(2000);
System.out.println("Thread name"+Thread.currentThread().getName()+":"+ ThreadLocalDemo.nextId);*/
ThreadLocalDemo threadLocalDemo =new ThreadLocalDemo();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 5, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
for (int i = 0; i <3 ; i++) {
threadPoolExecutor.execute(threadLocalDemo);
}
boolean flag=true;
while (flag) {
if (threadPoolExecutor.getCompletedTaskCount()==3) {
Runnable runnable = () -> {
//ThreadLocalDemo.set();
System.out.println("执行第二次任务 Thread name=>" + Thread.currentThread().getName() + ":" + ThreadLocalDemo.threadId.get());
};
threadPoolExecutor.execute(runnable);
flag=false;
}
}
threadPoolExecutor.shutdown();
}
}
//output
Thread name=>pool-1-thread-1:2
Thread name=>pool-1-thread-3:3
Thread name=>pool-1-thread-2:1
执行第二次任务 Thread name=>pool-1-thread-1:2
看上面代码第一批任务执行完成,线程归还到线程池中,且保存了ThreadLocal数据,当其他任务再次进来时从池中获取了原来的线程执行任务, 这样的话会有使用旧的线程处理新的请求,并且该线程存储了共享变量的值。
但是如果操作不当,可能会出现请求处理错误问题。
5.应用场景
ThreadLocal 提供了一种线程级别的数据存储机制。每个线程都拥有自己独立的 ThreadLocal 副本,这意味着每个线程都可以独立地、安全地操作这些变量,而不会影响其他线程。
ThreadLocal其实在工作中应该是非常常见的,以下是一些比较典型的使用场景:
- 用户身份信息存储:在很多应用中,都需要做登录鉴权,一旦鉴权通过之后,就可以把用户信息存储在ThreadLocal中,这样在后续的所有流程中,需要获取用户信息的,直接取ThreadLocal中获取就行了。非常的方便。
- 线程安全:ThreadLocal可以用来定义一些需要并发安全处理的成员变量,比如SimpleDateFormat,由于SimpleDateFormat 不是线程安全的,可以使用 ThreadLocal 为每个线程创建一个独立的 SimpleDateFormat 实例,从而避免线程安全问题。
- 日志上下文存储:在Log4j等日志框架中,经常使用ThreadLocal来存储与当前线程相关的日志上下文。这允许开发者在日志消息中包含特定于线程的信息,如用户ID或事务ID,这对于调试和监控是非常有用的。
- traceld存储:和上面存储日志上下文类似,在分布式链路追中中,需要存储本次请求的traceld,通常也都是基于ThreadLocal存储的。
- 数据库Session:很多ORM框架,如Hibernate、Mybatis,都是使用ThreadLocal来存储和管理数据库会话的。这样可以确保每个线程都有自己的会话实例,避免了在多线程环境中出现的线程安全问题。
- PageHelper分页:PageHelper是MyBatis中提供的分页插件,主要是用来做物理分页的。我们在代码中设置的分目页参数信息,页码和页大小等信息都会存储在ThreadLocal中,方便在执行分页时读取这些数据。
6.主要作用
- 解决并发问题
- 在线程中传递数据,在同一个线程执行过程中,ThreadLocal的数据一直在,所以我们可以在前面把数据放到ThreadLocal中,然后再后面的时候再取出来用,就可以避免要把这些数据一直通过参数传递。
分享到: