ThreadLocal
介绍
该类提供线程局部变量。 这些变量与其正常对应变量的不同之处在于,访问一个变量(通过其 get 或 set 方法)的每个线程都有其自己的、独立初始化的变量副本。 ThreadLocal 实例通常是类中希望将状态与线程关联起来的私有静态字段(例如,用户 ID 或事务 ID)。
只要线程处于活动状态并且 ThreadLocal 实例可访问,每个线程就持有对其线程局部变量副本的隐式引用; 线程消失后,其线程本地实例的所有副本都将受到垃圾回收(除非存在对这些副本的其他引用)。
是一种将变量和线程绑定的方案。
使用
用户登录登出动作,用户信息保存。
静态变量保存
/**
* 本地变量测试
*
* @author dcx
* @since 2023-06-26 14:57
**/
public class NoThreadLocalDemo {
//类属性保存当前用户名
private static String curName;
public void login(String name) {
System.out.println(name + " login");
curName = name;
}
public void getCur() {
System.out.println(curName+" get");
}
public void logout() {
System.out.println(curName+" logout");
curName = null;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
NoThreadLocalDemo threadLocalDemo = new NoThreadLocalDemo();
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(() -> {
threadLocalDemo.login(1 + "");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadLocalDemo.getCur();
threadLocalDemo.logout();
});
executorService.execute(() -> {
threadLocalDemo.login(2 + "");
threadLocalDemo.getCur();
threadLocalDemo.logout();
});
System.out.println("------");
}
}本例中,用户1登录,延迟执行。用户2登录不延迟执行。
执行结果:
------
1 login
2 login
2 get
2 logout
null get
null logout可以看出,线程2将用户信息释放,用户1延迟之后的执行出现错误。
threadLocal方式
public class ThreadLocalDemo {
private static final ThreadLocal<String> curName = new ThreadLocal<>();
public void login(String name) {
System.out.println(name + " login");
curName.set(name);
}
public void getCur() {
String s = curName.get();
System.out.println(s + " get");
}
public void logout() {
System.out.println(curName.get() + " logout");
curName.remove();
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(() -> {
threadLocalDemo.login(1 + "");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadLocalDemo.getCur();
threadLocalDemo.logout();
});
executorService.execute(() -> {
threadLocalDemo.login(2 + "");
threadLocalDemo.getCur();
threadLocalDemo.logout();
});
System.out.println("---------");
}
}用户1登录,延迟执行。用户2登录不延迟执行。
2 login
2 get
2 logout
---------
1 login
1 get
1 logout可以看出,用户1和用户2正常执行。
核心原理
set
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程中的threadlocalMap,每个线程都会有一个threadlocalMap属性。
ThreadLocalMap map = getMap(t);
if (map != null) {
//key是threadLocal,value是具体的值。
map.set(this, value);
} else {
//如果是InheritableThreadLocal则会赋值给Thread的inheritableThreadLocals
createMap(t, value);
}
}get
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//ThreadLocalMap保存Entry集合。根据thradLocal,从ThreadLocalMap中获取
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}thread,threadLocal,threadLocalMap,Entry关系:
thread包含threadLocalMap属性。
threadLocal封装了对threadLocalMap的操作。
threadLocalMap维护着Entry列表。
Entry是一种弱引用,key是threadLocal,value是具体的值。
ThreadlocalMap
是一种专门为保存线程本地变量而设计的一种hash map。
set
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//计算所在数组下标
int i = key.threadLocalHashCode & (len-1);
//1.优先查看现有的数组下标是否有值。
//2.如果当前数组下标有值,则会循环向后1个位置获取,直到所在数组位置没有值。
//3.此循环是解决hash冲突或者多个hash取余后,槽位已经有值的情况下,则会遍历向后存放元素。
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;
}
}
//经过遍历后,没有找到槽位,进行扩容,幅度是加1。
tab[i] = new Entry(key, value);
int sz = ++size;
//如果数组长度超过负载,则会进行rehash。
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}resize
rehash里面主要是会进行resize。重点看下这个:
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
//新数组扩大2倍
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
//重新进行hash计算,放进数组。
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}get
private Entry getEntry(ThreadLocal<?> key) {
//通过hash值和数组长度,确定槽位。
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
//如果当前槽位没有,说明有hash冲突,因此进行循环查找。
return getEntryAfterMiss(key, i, e);
}不足
无法将变量传递到子线程。
public void getCur() {
String s = curName.get();
System.out.println(s + " get");
new Thread(
()->{
System.out.println("子线程获取name:"+curName.get());
}
).start();
}输出:
1 login
---------
2 login
2 get
2 logout
子线程获取name:null
1 get
1 logout
子线程获取name:nullInheritableThreadLocal 对此做出了优化。
Last updated
Was this helpful?