对于ThreadLocal这个类既熟悉又陌生。熟悉是因为面试经常会被问到,所以背了很多概念;陌生是因为实际并没怎么用过(惭愧,我是菜鸡)。
这个周末不想出门,百无聊赖之中简单扒一扒它的用法。
ThreadLocal简介
ThreadLocal这个类在java的lang包下,和想象中不一样,它其实跟Thread本身没有直接关系,并没有继承Thread。
image-20230722174049473.png
比较关键的两个方法:get(),set()。
其中有个内部类:ThreadLocalMap也比较重要。
感兴趣的可以阅读下源码,加上注释只有720行,比较简单。
ThreadLocal的主要作用
- 用于线程间的数据隔离,可以把它想象成一个存放变量的瓶子,每个线程过来访问的时候都会拿到一只,想要用变量的时候就把它从瓶子里拿出来,或者放一个新的进去,然后把盖子拧上,这样其他线程就拿它无可奈何啦。或者说得正式点:ThreadLocal为每个线程中的并发访问变量提供一个副本。
注意要先放再取,不然会硌到手
- 减少线程同步的资源消耗
- Session会话处理
- 存储线程事务信息
使用示例
创建一个类ContextUtil,这个类我们把它比喻成一个柜子,可以用来放很多ThreadLocal的瓶子。我们在里面放上两个瓶子,一个瓶子放User对象,一个瓶子放Integer对象。
public class ContextUtil {
public static ThreadLocal<User> contextUser = new ThreadLocal<>();
public static ThreadLocal<Integer> contextCount = new ThreadLocal<>();
}
User类
@Data
public class User implements Serializable {
private Integer userId;
private String userName;
}
现在测试一下:启动三个线程,每个线程都会从ContextUtil中拿到自己的两个ThreadLocal,并各自往里面放进对象,过几秒后再取出来确认一下有没有被其他线程偷偷换掉‘
public static void main(String[] args) {
Thread t0 = new Thread(){
@SneakyThrows
@Override
public void run() {
ContextUtil.contextUser.set(new User(1, "1号"));
ContextUtil.contextCount.set(1);
//打开瓶子看一下
System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextUser.get());
System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextCount.get());
//强迫症犯了,再看一下
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextUser.get());
System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextCount.get());
}
};
t0.start();
Thread t1 = new Thread(){
@SneakyThrows
@Override
public void run() {
ContextUtil.contextUser.set(new User(2, "2号"));
ContextUtil.contextCount.set(2);
//打开瓶子看一下
System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextUser.get());
System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextCount.get());
//强迫症犯了,再看一下
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextUser.get());
System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextCount.get());
}
};
t1.start();
Thread t2 = new Thread(){
@SneakyThrows
@Override
public void run() {
ContextUtil.contextUser.set(new User(3, "3号"));
ContextUtil.contextCount.set(3);
//打开瓶子看一下
System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextUser.get());
System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextCount.get());
//强迫症犯了,再看一下
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextUser.get());
System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextCount.get());
}
};
t2.start();
}
结果
Thread-2-----User(userId=3, userName=3号)
Thread-2-----3
Thread-0-----User(userId=1, userName=1号)
Thread-1-----User(userId=2, userName=2号)
Thread-1-----2
Thread-0-----1
Thread-0-----User(userId=1, userName=1号)
Thread-0-----1
Thread-2-----User(userId=3, userName=3号)
Thread-2-----3
Thread-1-----User(userId=2, userName=2号)
Thread-1-----2
小结
从示例中可以看到:三个线程各自为ContextUtil中的两个ThreadLocal设置了不同的值,并且在整个运行期间都没有发生污染
ThreadLocal确实为每个线程创建了单独的变量副本