ThreadLocal是什么
ThreadLocal 是 java.lang包下的一个类,用于在多线程环境中保存线程私有变量。
会用再说
当一个线程执行过程中需要共享某个值,且这个值属于线程私有,可以使用ThreadLocal。
例如:Web开发中的请求处理过程。每个请求通常由一个线程来处理,而不同请求之间的数据是相互独立的。使用 ThreadLocal 可以很方便地在请求处理过程中共享数据,比如用户信息,看个例子:
public class UserContext {
// 使用 ThreadLocal 存储用户信息
private static ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
public static void setUser(User user) {
userThreadLocal.set(user);
}
public static User getUser() {
return userThreadLocal.get();
}
public static void clearUser() {
userThreadLocal.remove();
}
}
设计一个UserContext 类,用于存储用户信息,内部使用 ThreadLocal 实现。接下来模拟一下请求处理过程。
//模拟请求处理过程
public class WebRequestProcessor {
//请求处理过程
public void processRequest(int userId) {
// 从数据库中获取用户信息
User user = UserRepository.getUserById(userId);
// 将用户信息保存到UserContext中
UserContext.setUser(user);
// 整个请求处理过程中可以随时获取用户信息
performAuthentication();
performAuthorization();
//...
// 处理完请求后清除用户信息
UserContext.clearUser();
}
private void performAuthentication() {
// 获取用户身份,并验证...
User user = UserContext.getUser();
System.out.println("Authenticating user: " + user.getUsername());
}
private void performAuthorization() {
// 获取用户身份,并授权...
User user = UserContext.getUser();
System.out.println("Authorizing user: " + user.getUsername());
}
}
请求处理过程中,从数据库中获取用户信息并设置到UserContext中。整个处理流程中,可以随时通过 UserContext.getUser() 获取当前线程的用户信息。
以上例子没有明确体现出多线程间互不影响,再来看一个简单的例子:
public class ThreadLocalExample {
//多线程共用的ThreadLocal
private static ThreadLocal<Integer> threadLocalVariable = new ThreadLocal<>();
public static void main(String[] args) {
// 创建并启动三个线程
Thread thread1 = new Thread(() -> {
threadLocalVariable.set(1);
printThreadLocalVariable();
},"线程1");
Thread thread2 = new Thread(() -> {
threadLocalVariable.set(2);
printThreadLocalVariable();
},"线程2");
Thread thread3 = new Thread(() -> {
threadLocalVariable.set(3);
printThreadLocalVariable();
},"线程3");
thread1.start();
thread2.start();
thread3.start();
}
private static void printThreadLocalVariable() {
// 从 ThreadLocal 中获取变量值,并打印
int value = threadLocalVariable.get();
System.out.println(Thread.currentThread().getName() + ": ThreadLocal Variable = " + value);
}
}
执行结果:
线程1: ThreadLocal Variable = 1
线程3: ThreadLocal Variable = 3
线程2: ThreadLocal Variable = 2
使用ThreadLocal作为容器,创建了三个线程,每个线程都通过 threadLocalVariable.set() 方法将自己私有的值存入ThreadLocal,线程执行过程中通过 printThreadLocalVariable() 方法从ThreadLocal 中获取变量值并打印。
从结果可以看出,每个线程取出的值都是自己之前设置过的,属于自己的值,并不会拿错,多线程间独立。
很多人都把物品放到一个公共大容器里,要用的时候也直接从大容器里取,还能保证不拿错???太神奇了!
得去看看原理↓ ↓ ↓
实现原理
上源码,先大致过一下。
public class ThreadLocal<T> {
//构造方法
public ThreadLocal() {
}
//存入值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//获取值
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();
}
//删除值
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
//获取线程的threadLocals
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
}
以上是ThreadLocal源码中主要的方法,下面依次看看各个方法的调用过程。
getMap
通过源码发现,每个方法执行前都先获取到_当前线程对象_,把当前线程作为参数传给getMap()方法,而getMap()方法中仅有一行代码:返回当前线程的threadLocals。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看出threadLocals是Thread类的一个成员变量,具体是什么?还得去Thread源码中一探究竟。
class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
}
threadLocals是一个ThreadLocal.ThreadLocalMap类型的成员变量。通过类型可知,是ThreadLocal类的静态内部类(详细结构见ThreadLocal源码)。
通过ThreadLocal中的ThreadLocalMap结构可以看出,ThreadLocalMap是一个专为_维护线程私有值_而定制的哈希表。
每个线程都有一个ThreadLocalMap,通过ThreadLocal存放的数据都是放到了这里。
原来,存放的数据是在“自己家里”,而非公共容器里。
具体如何存取的??↓ ↓ ↓
set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //获取当前对象内部的map
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
先获取当前线程内部的map。
若非空,将this作为键,参数value作为值,存入map中。因为是通过ThreadLocal对象调用set,所以this就是ThreadLocal对象。
否则,创建一个ThreadLocalMap对象赋值给当前线程的threadLocals变量。
原来如此~
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();
}
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;
}
protected T initialValue() {
return null;
}
同样,get时,先获取当前线程内部的map。
非空,则将this作为键,查询出value并返回。
否则!来了一个迷惑行为,调用setInitialValue(),通过方法名感觉是搞一个初始值,进入逛逛,原来是一场null~
remove
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
同样,获取线程内部map,非空,以this为key,移除。
通透了~原来ThreadLocal只是个壳!只是个key,真正的数据,还是存在线程内部(取的时候去自己家里取,怎么可能取错呢),操作数据时,先拿到当前线程的map,以ThreadLocal对象为key,对数据进行存取。
总结
ThreadLocal可以当做一个全局大容器使用,多个线程共同往里面存取数据,且保证多线程间存取独立。
实际上,往ThreadLocal中存的数据是放在每个线程内部的map对象中。操作数据前,先获取当前线程的map,以ThreadLocal对象为key进行数据存取。