ThreadLocal原理:
字段:
//ThreadLocal对象的哈希码
private final int threadLocalHashCode = nextHashCode();
//生成ThreadLocal对象的哈希码时,需要用到该对象,从0开始
private static AtomicInteger nextHashCode =
new AtomicInteger();
//哈希码的增长值
private static final int HASH_INCREMENT = 0x61c88647;
nextHashCode()方法:
private static int nextHashCode() {
//通过AtomicInteger对象生成ThreadLocal对象的哈希码并返回
//步长为:HASH_INCREMENT
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
方法实现:
(1)get()方法:
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//判断ThreadLocalMap对象是否为空
//map不为null说明有键值对
if (map != null) {
//在map中,ThreadLocal作为key
ThreadLocalMap.Entry e = map.getEntry(this);
//如果key不为空
if (e != null) {
@SuppressWarnings("unchecked")
//通过key获取值并返回
T result = (T)e.value;
return result;
}
}
//如果map为null,则创建map
//当前ThreadLocal对象作为key,null作为value存入map中
//返回null
return setInitialValue();
}
(2)set()方法:
public void set(T value) {
//获取当前线程的ThreadLocalMap对象
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//如果map不为null
if (map != null)
//将当前ThreadLocal作为key
//参数value作为值,存入ThreadLocalMap中
map.set(this, value);
else
//map为null,则创建map
//将当前ThreadLocal对象作为firstKey,参数value作为firstValue
createMap(t, value);
}
通过set方法,我们可以知道:
1、线程调用ThreadLocal对象的set方法时,会获取当前线程的ThreadLocalMap对象。
2、随后会将当前ThreadLocal对象作为key,参数value作为值存储到线程内部的ThreadLocalMap对象中。
3、这意味着即使ThreadLocal是多个线程共享的变量也不会存在线程安全问题,因为每个线程都只在操作自己的ThreadLocalMap,ThreadLocal只是作为key保存在map中。
(3)remove()方法:
public void remove() {
//获取当前线程的ThreadLocalMap对象
ThreadLocalMap m = getMap(Thread.currentThread());
//如果map不为null
if (m != null)
//将当前ThreadLocal对象从ThreadLocalMap中移除
m.remove(this);
}
ThreadLocalMap、ThreadLocal、Thread三者之间的联系:
测试:
(1)User类:
package test;
public class User {
private Integer id;
private String name;
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
public User(){
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
(2)UserHolder类:
package test;
public class UserHolder {
private static final ThreadLocal<User> threadLocal = new ThreadLocal<>();
public static void set(User user){
threadLocal.set(user);
}
public static User get(){
User user = threadLocal.get();
return user;
}
}
ThreadLocal作为静态字段存在于UserHolder类中,UserHolder的set方法、get方法实际上都是由ThreadLocal实现,这里使用到了组合的思想。
(3)ThreadLocalTest类:
package test;
public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println("Thread-1线程运行,存储User对象到TheadLocal中...");
createAndHold(1,"张三");
User user = UserHolder.get();
System.out.println(user);
}, "Thread-1").start();
System.out.println("main线程阻塞...");
Thread.sleep(3000);
System.out.println("main线程运行,获取UserHolder存储的User对象...");
User user = UserHolder.get();
System.out.println("user对象:" + user);
}
private static void createAndHold(Integer id,String name){
User user = new User(id, name);
UserHolder.set(user);
}
}
代码解读:
1、main线程一开始会阻塞3s,Thread-1线程运行,将User对象存入ThreadLocal并打印。
2、main线程解除阻塞后,会去获取ThreadLocal中存储的User对象;照理来说,ThreadLocal对象是静态成员,对于两个线程来说是共享变量,此时main线程应该会获取到Thread-1存入的User对象。
3、但我们知道,ThreadLocal真正操作的是每个线程内部的ThreadLocalMap,ThreadLocal对象只作为key存储到每个线程自己的ThreadLocalMap中,我们可以通过key找到value,实现线程间的隔离。
测试结果:
ThreadLocal使用场景:
(1)多线程隔离: 多个线程使用ThreadLocal访问共享变量,每个线程都会得到共享变量的一个副本,后续多个线程自己所属副本的所有操作不会冲突,没有线程安全问题。
(2)线程内共享:ThreadLocal 可以用于在整个线程生命周期内共享这些上下文信息,而不需要显式地传递参数。
(3)存储用户信息:在Web应用中,可能会在用户登录后将用户身份信息存储在 ThreadLocal 中,以便在整个请求处理过程中方便地访问用户信息,而不必在每个方法中都传递用户信息参数。