ThreadLocal是java.lang包下面的一个类,与线程息息相关. 作为一个刚入门的小码农,平时代码开发基本没怎么用到过,偶然间接手了一个大牛的项目(你懂的)见到了这个类,发现了它的强大之处,相见恨晚啊!
ThreadLocal可以设置某个全局变量的当前线程局部值!
说起来很绕口,就是指:每个线程都可以看到这个变量,但是每个线程看到的这个变量的值是不一样的。这个变量对于所有线程来说是全局的,但这个变量的值对每一线程来说都是局部的。每个线程都可以对这个变量操作,但各个线程对这个变量的操作结果互不影响。(简单来说,就是每个线程对应这个变量都有不同的值)
一般的项目开发中,我们设置的接口、类、变量、方法(public 、private、protected、默认修饰符)都是对所有线程公开的,各个线程的操作结果会相互影响,(一个最简单的问题,多进程高并发情况下可能会导致属性的覆盖)而ThreadLoca恰恰就解决了这个问题。
下面先说一下用处:
ThreadLocal可以隐性传参
比如:用户登录,在登录时我就获取用户的信息,放进线程中,这样在调用其他方法时就不必携带用户信息作为方法参数了,可以直接从线程里面取。
ThreadLocal可以保证变量值在任何时候不被其他线程覆盖
比如:数据库的连接池,使用ThreadLocal管理Connection,保证事务的一致性。
下面简单讲解一下:
public class ThreadLocal<T> {
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);
}
其他内容暂时省略
....
}
T 即为你准备放进去的数据类型(最常见的就是放一些自定义类,比如放用户的登录信息UserInfo)
使用的时候可以
ThreadLocal<UserInfo> user=new ThreadLocal<>;
创建完ThreadLocal对象后就可以直接调用set方法放入用户信息;
使用时,直接调用get方法;
业务结束后可以再调用remove方法清除用户信息。
如果是使用spring框架,可以借助AOP来完成用户信息的放入与清除
注:如果使用了线程池,则必须要在线程复用之前清除上次放入的信息,否则会造成内存的泄露,数据混乱。
写一个小用法示例:
public class ThreadLocalUtil{
private static ThreadLocal<UserInfo> user=new ThreadLocal<>();
public static void set(UserInfo userInfo){
user.set(userInfo);
}
public static void remove(){
user.remove();
}
public static void get(){
return user.get();
}
}
在Spring Aop 动态放入用户信息后, 在调用时 UserInfo user=ThreadLocalUtil.get(); 即可获得存储在当前线程的用户信息
下面讲一下ThreadLocal的原理:
ThreadLocal类:
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);
}
//ThreadLocalMap是ThreadLocal的内部类
static class ThreadLocalMap {
其他内容暂时省略
...
}
ThreadLocal类只有一个无参构造方法,且并没用任何操作。
创建完对象后就可以调用set方法放进对象了。
set 方法里面首先得到了当前线程,
有从当前线程中调用getMap方法得到ThreadLocalMap对象
-----------------------------------------------------------------------
ThreadLocalMap类:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
这里又从线程里得到了当前线程的threadLocals;
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
-----------------------------------------------------------------------
Thread类:
/** ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class */
ThreadLocal.ThreadLocalMap threadLocals = null;
-----------------------------------------------------------------------
由此我们可以看出ThreadLocalMap是ThreadLocal的内部类,使用Entry进行存储
Thread类维护一个ThreadLocalMap对象(默认为null);
我们回到ThreadLocal类的set方法,代码拷贝过来
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
如果map为空就创建一个ThreadLocalMap(Thread类的ThreadLocalMap对象默认为null),并把值放进去; 不为空就map.set(this,value)放进去 方法其实是内部类ThreadLocalMap的set方法(使用Entry存储,Entry是ThreadLocalMap的内部类),代码拷贝过来
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
这便是ThreadLocal set方法的完整流程了,get方法同理就不做介绍了,建议参照源码深入理解下,加深理解。
这里放一张ThreadLoacal对象关系引用图
注:如果使用了线程池,则必须要在线程复用之前清除上次放入的信息,否则会造成内存的泄露,数据混乱。