ThreadLocal 线程的变量副本,实现每个线程都有自己专属本地变量

ThreadLocal.png

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的数据结构

ThreadLocalMap

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

referent
这里创建的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);