ThreadLocal类
该类提供了线程内局部 (thread-local) 变量。
好比有两个窗口(两个线程),一个窗口可以拿饮料,一个窗口可以拿食物。现在有多个人要来拿东西,如果在饮料窗口有的人拿到了饮料,有的人拿到了不该拿的食物,就说明线程之间出现了混乱,我们应当避免这种情况出现。
以下代码就可能会出现线程混乱的问题:
private static int data;
public static void main(String[] args) {
for (int i = 0; i < 2 ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
//线程中的数据
int data = new Random().nextInt();
ThreadLocalDemo.data = data;
System.out.println(Thread.currentThread().getName()+"上的数据是:"+ data);
//模块A获取数据
System.out.println(Thread.currentThread().getName()+"上A模块取得数据:"+ new A().get());
//模块B获取数据
System.out.println(Thread.currentThread().getName()+"上B模块取得数据:"+ new B().get());
}
}).start();
}
}
//模块A
static class A{
public static int get(){
return data;
}
}
//模块B
static class B{
public static int get(){
return data;
}
}随机的打印结果如下:
Thread-0上的数据是:-312244177
Thread-1上的数据是:-1541655481
Thread-0上A模块取得数据:-312244177
Thread-1上A模块取得数据:-312244177
Thread-0上B模块取得数据:-312244177
Thread-1上B模块取得数据:-312244177
可以看出,在线程1上取到了线程0的数据,说明线程之间产生了混乱。
set方法和get方法
不再通过成员变量来存储值了,而是使用ThreadLocal类的set方法来存储值,再直接通过get方法来获取值。set方法传递存储的数据,在线程内使用get方法无需传递参数,默认就是当前线程。

//创建ThreadLocal对象,泛型为要存储的数据的类型
private static ThreadLocal<Integer> local = new ThreadLocal<Integer>();
public static void main(String[] args) {
for (int i = 0; i < 2 ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
//线程中的数据
int data = new Random().nextInt();
//使用ThreadLocal的set方法将数据存到底层的Map集合中
local.set(data);
System.out.println(Thread.currentThread().getName()+"上的数据是:"+ data);
//模块A获取数据
System.out.println(Thread.currentThread().getName()+"上A模块取得数据:"+ new A().get());
//模块B获取数据
System.out.println(Thread.currentThread().getName()+"上B模块取得数据:"+ new B().get());
}
}).start();
}
}
//模块A
static class A{
public static int get(){
return local.get();
}
}
//模块B
static class B{
public static int get(){
return local.get();
}
}set底层
在 set 方法执行时,会首先根据当前线程获取当前线程的ThreadLocalMap对象,然后往这个map中插入一条记录:key 其实是ThreadLocal对象,用于记录当前线程唯一的局部标识符(在第一次自动调用 UniqueThreadIdGenerator.getCurrentThreadId()时分配的,在后续调用中不会更改。),value是各自的set方法传进去的值。这样就记录了线程和当前线程中数据的映射绑定关系。
一个ThreadLocal对象可以多个线程共用。可以直接看做是一个Map容器,用来存储当前线程和对应值的键值对。因为键的唯一性,所以一个ThreadLocal对象在一个线程内只能存储一个数据;如果要在一个线程内存储多条数据,就需要创建多个ThreadLocal对象来存取数据,比较麻烦。
在线程结束时可以调用 ThreadLocal.remove()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的 ThreadLocal 变量。
存储多个数据
可以将一个线程内的多个数据封装成一个实体类,然后使用set方法存入一个实体类对象,再取出一个实体类对象。通常使用单例模式来设计该实体类对象,并将实体类和ThreadLocal对象进行绑定。
我们可以将ThreadLocal隐藏起来,使其对外部而言只用关注数据即可。优雅的设计如下:
public class ThreadLocalMap2 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
//将数据封装到User对象中
User user = User.getUser();
user.setName("张三");
user.setAge(20);
//模块A获取数据
new A().get();
//模块B获取数据
new B().get();
}
}).start();
}
//模块A
static class A{
public static void get(){
User user = User.getUser();
System.out.println("A模块获取的姓名是:"+user.getName()+",年龄是:"+user.getAge());
}
}
//模块B
static class B{
public static void get(){
User user = User.getUser();
System.out.println("B模块获取的姓名是:"+user.getName()+",年龄是:"+user.getAge());
}
}
//存储数据的实体类
static class User{
private String name;
private Integer age;
private static ThreadLocal<User> local = new ThreadLocal<>();
private User() {
}
public synchronized static User getUser() {
//让一开始获取的User就在ThreadLocal中
User user = local.get();
if(user == null){
user = new User();
local.set(user);
}
return user;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
}