ThreadLocal
What
英文API文档:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
谷歌翻译:
此类提供线程局部变量。 这些变量与普通变量不同,因为每个访问一个线程(通过其get或set方法)的线程都有其自己的,独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或交易ID)。
个人建议:如果能阅读英文文档的话,还是先从英文文档入手。
ThreadLocal:thread-local variables应该翻译成线程本地变量,会更容易理解一些。通过ThreadLocal为每一个线程设置变量,这个变量相对于多线程,就是某一个线程的本地变量,只有对应的线程能访问。
通常的使用方式:私有静态字段;
我们先从源代码入手,ThreadLocal位于java.lang包下,所以我们不用导包就可以使用。
了解ThreadLocal,我们先从set(T 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);
}
可以看到set(T value)方法里面涉及到了ThreadLocalMap,这个类其实就是ThreadLocal的内部静态类,在ThreadLocal里面可以找到。
ThreadLocalMap里面又定义了一个Entry用于存储键(key)与值(value)。
Entry代码:
static class Entry extends WeakReference<ThreadLocal<?>{
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
值得一提的是:
Entry的key是ThreadLocal对象,value是我们设置的值;
Entry在ThreadLocalMap里面是一个数组Entry[];
知道了以上这些后,返回到set(T value)方法,可以看到getMap(t)这个方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
注意,t 是 Thread.currentThread()获取的,也就是本地线程。t.threadLocals,就是本地线程的threadLocals这个参数,我们看看Thread的threadLocals这个参数:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
可以看到ThreadLocalMap是在Thread里面获取的
再看看createMap(t, value);方法:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
也就是说,当Thread里面threadLocals=null时,就会创建一个ThreadLocalMap并赋值给thread的threadLocals,也就是说,每一个thread通过ThreadLocal都将会持有一个ThreadLocalMap。
如果set(T value)里的map已存在,那么就把value设置到map里面。
接下来说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();
}
get()方法先getMap(t),也就是先从当前Thread中获得ThreadLocalMap,通过key为当前ThreadLocal对象获得value;
如果找不到map,就会执行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;
}
注意
initialValue();这个方法,重新这个方法,我们可以自动生成或默认本地线程变量;
Why
看了源代码,其实也是有点乱的,说说我个人的理解。
ThreadLocal使每个Thread都有持有一个ThreadLocalMap,而ThreadLocalMap定义了一个Entry[]数组,Entry保存了[key=ThreadLocal,value=Object]键值对。
而ThreadLocal的get方法,是用过找到ThreadLocalMap后用ThreadLocal对象为key找到相应的value。
How
官方给的例子:
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}
接下来,代码模拟10个用户登录:一个用户开启一个线程并设置用户信息到线程本地变量,然后获取线程本地变量,模拟登录;
public class Test {
public static void main(String []args) {
//初始化10个线程,模拟10个用户登录
for ( int i = 0 ; i < 10 ; i ++ ) {
new Thread( "Thread" + i ){
@Override
public void run(){
//模拟用户信息
User user = new User( "user" + Thread.currentThread().getName() );
//把用户信息设置到ThreadLocalHelper里面
ThreadLocalHelper.setUserInfo(user);
//登录
Function.login();
}
}.start();
}
}
}
class Function {
//模拟登录
public static void login() {
//从ThreadLocalHelper获取当前线程的用户信息
User user = ThreadLocalHelper.getUserInfo();
if ( null != user.getName() ) {
System.out.println("login success [ThreadName=" + Thread.currentThread().getName() + ",UserName=" + user.getName() + "]");
}
}
}
class ThreadLocalHelper{
//私有静态的ThreadLocal
private static ThreadLocal<User> userInfo = new ThreadLocal<User>();
//保存user
public static void setUserInfo(User user){
userInfo.set(user);
}
//取出user
public static User getUserInfo(){
return userInfo.get();
}
}
//User对象
class User{
private String name;
public User(String name){
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName(){
return this.name;
}
}
运行结果:
结果中的ThreadName是当前线程的名称,UserName是访问线程本地变量后取出来的值,由user+线程名称组成。
结论:通过ThreadLocal线程可以正确获取已存储线程本地变量。
When & Where
当某些值需要绑定到线程的时候。
ThreadLocal提供线程局部变量,确保每个线程拥有独立的变量副本。它常用于存储线程相关的状态,如用户ID或交易ID。使用ThreadLocal,可以通过get和set方法访问线程的本地变量。在Thread类中有一个ThreadLocalMap,用于存储键值对。当首次调用set方法时,会为当前线程创建ThreadLocalMap。ThreadLocal的get方法通过键查找ThreadLocalMap中的值。通过示例展示了如何在线程中设置和获取用户登录信息,适用于多线程场景下需要绑定线程特定数据的情况。
1958

被折叠的 条评论
为什么被折叠?



