ThreadLocal 线程的变量副本,实现每个线程都有自己专属本地变量
ThreadLocal代码演示
package leetcode.test;
import java.util.ArrayList;
import java.util.List;
public class ThreadLocalDemo {
private List<String> messages = new ArrayList<>();
public static final ThreadLocal<ThreadLocalDemo> holder = ThreadLocal.withInitial(ThreadLocalDemo::new);
public static void add(String message) {
holder.get().messages.add(message);
}
public static List<String> clear() {
List<String> messages = holder.get().messages;
holder.remove();
System.out.println("size: " + holder.get().messages.size());
return messages;
}
public static void main(String[] args){
ThreadLocalDemo.add("hello wrold");
System.out.println(holder.get().messages);
ThreadLocalDemo.clear();
}
}
输出
[hello wrold]
size: 0
ThreadLocal对象可以提供线程局部变量,每个线程Thread拥有一份自己的副本变量,多个线程互不干扰
ThreadLocal的数据结构
Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap
ThreadLocalMap有自己的独立实现,可以简单将它的key视作ThreadLocal,value为代码中放入的值。(实际上key并不是ThreadLocal本身,而是它的一个弱引用)
每个线程在往ThreadLocal里放值的时候,都会往自己的ThreadLocalMap里存,读也是以ThreadLocal作为引用,在自己的map里找相对应的key,从而实现了线程隔离
ThreadLocalMap有点类似HashMap结构,只是HashMap是由数组+链表实现,而ThreadLocalMap中并没有链表结构
还要注意Entry,它的key是ThreadLocal<?> k,继承自WeakReference,弱引用类型
GC 之后key是否为null
ThreadLocal 的key 是弱引用,那么在ThreadLocal.get()时,发生GC后,key是否为null
Java的四种引用类型
- 强引用:new出来的对象就是强引用类型,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足的时候
- 软引用:使用SoftReference修饰的对象被称为软引用,软引用指向的对象在内存要溢出的时候被回收。
- 弱引用:使用WeakReference修饰的对象称为弱引用,只要发生垃圾回收,若这个对象只被弱引用,那么就会被回收
- 虚引用:虚引用是最弱的引用,在Java中使用PhantomReference进行定义。虚引用中唯一的作用就是用队列接受对象即将死亡的通知
package leetcode.test;
import java.lang.reflect.Field;
public class ThreadLocalDemo1 {
public static void main(String[]args) throws InterruptedException {
Thread t = new Thread(()->test("abc",false));
t.start();
t.join();
System.out.println("--gc后");
Thread t2 = new Thread(()->test("Def",true));
t2.start();
t2.join();
}
private static void test(String s,boolean isGC){
try {
new ThreadLocal<>().set(s);
if (isGC) {
System.gc();
}
Thread t = Thread.currentThread();
Class<? extends Thread> clz = t.getClass();
Field field = clz.getDeclaredField("threadLocals");
field.setAccessible(true);
Object ThreadLocalMap = field.get(t);
Class<?> tlmClass = ThreadLocalMap.getClass();
Field tableField = tlmClass.getDeclaredField("table");
tableField.setAccessible(true);
Object[]arr = (Object[]) tableField.get(ThreadLocalMap);
for (Object o :arr){
if (o!=null){
Class<?> entryClass = o.getClass();
Field valueField = entryClass.getDeclaredField("value");
Field referenceField = entryClass.getSuperclass().getSuperclass().getDeclaredField("referent");
valueField.setAccessible(true);
referenceField.setAccessible(true);
System.out.println(String.format("弱引用key:%s,值:%s",referenceField.get(o),valueField.get(o)));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果如下:
弱引用key:java.lang.ThreadLocal@17a71266,值:abc
弱引用key:java.lang.ThreadLocal@5b225cd5,值:java.lang.ref.SoftReference@126b14f1
--gc后
弱引用key:null,值:Def
这里创建的ThreadLocal并没有指向任何值,也就没有任何引用
new ThreadLocal<>().set(s);
所以在这GC后,key就会被回收,referent = null
ThreadLocal#set后会将threadLocal实例本身作为key 放入 Thread.currentThread().threadLocalMap中,与set的value构成一对Entry。而Entry使用了threadLocal的实例作为 弱引用。因此当发生gc的时候,弱引用的key会被回收掉,而作为强引用的value还存在。
如果改动一下代码:
// new ThreadLocal<>().set(s);
ThreadLocal<Object> threadLocal = new ThreadLocal<>();
threadLocal.set(s);
- 本文链接:https://wentianhao.github.io/2021/08/25/ThreadLocal/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。
若没有本文 Issue,您可以使用 Comment 模版新建。