月泉的博客

并发编程基础知识(中) 深度剖析ThreadLocal机制

月泉 并发编程

并发编程基础知识(中)

ThreadLocal是什么

Java中有一个类能够给线程提供线程的局部变量,使用这个类来设置值可以使得每个线程都有自己的一份变量副本,这个类就是ThreadLocal

ThreadLocal 的使用

class ThreadLocalTest{
    private static ThreadLocal<String> threadLocal = new ThreadLocal();
    public static void getThreadLocalAndPrint(){
        System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
    }
    public static void main(String[] args){
        var threadA = new Thread(() -> {
            threadLocal.set("ThreadA Message");
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            getThreadLocalAndPrint();
        });
        
        var threadB = new Thread(() -> {
            threadLocal.set("ThreadB Message");
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            getThreadLocalAndPrint();
        });
        
        threadA.start();
        threadB.start();
    }
}

控制台输出:

Thread-1:ThreadB Message
Thread-0:ThreadA Message

从代码的使用上可以了解到ThreadLocal用于设置整条线程的局部变量

ThreadLocal 的原理

打开ThreadLocal的源码,它暴露出来供外部使用的方法有4个

class ThreadLocal<T>{
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier){
        ...
    }
    public T get(){
        ...
    }
    
    public void set(T value){
        ...
    }
    
    public void remove(){
 		...       
    }
}

withInitial

这个方法是在JDK 1.8才添加的,它是一个静态方法,它的主要作用是用来创建一个有默认值的ThreadLocal,先得其用法再观其源码知其所以然

用法:

ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Default");

观其源码:

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
    return new SuppliedThreadLocal<>(supplier);
}

其返回值是一个ThreadLocal对象,接收的参数是一个Supplier的泛型对象,其返回的具体实例是一个SuppliedThreadLocal,那么可以得出一个结论就是,SuppliedThreadLocal肯定是ThreadLocal的子类,接着看下Supplier的源码

public interface Supplier<T> {
    T get();
}

这是一个很简单的接口,就是用于返回泛型类型的值,这是一个很典型的数据封装屏蔽掉不同数据类型的手法,接着来看下SuppliedThreadLocal的源码

static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

    private final Supplier<? extends T> supplier;

    SuppliedThreadLocal(Supplier<? extends T> supplier) {
        this.supplier = Objects.requireNonNull(supplier);
    }

    @Override
    protected T initialValue() {
        return supplier.get();
    }
}

从源码上可以得知该类是一个静态的不可继承的一个内部类,其主要是在创建时需要一个supplier对象,用于返回值,然后覆盖了父类的initialValue方法,其实现就是利用传进来的实例的get方法将值返回。

set

该方法主要是给当前线程设置一个局部变量,用法如下:

threadLocal.set("message");

源码:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}

初略的看下源码,首先是获取到当前线程,然后调用getMap将当前线程对象传入进入获取到一个ThreadLocalMap对象,然后再判断获取的map对象是否为空,如果不为空就直接set(this,value),将当前的threadLocal对象做为key存储进去,否则调用createMap创建一个map

粗略总结

  1. 获取到当前线程
  2. 根据当前线程获取到ThreadLocalMap
  3. 然后将当前的threadLocal作为key然后将值放到ThreadLocalMap中去

接着深入的剖析一下

getMap开始

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

从源码中了解到getMap实际上就是返回线程对象的threadLocals实例

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap是一个自定义的HashMap

map.set方法也没有什么好看的,实际上就是类似于HashMap的put存值,但它的实现比较简单,仅用了分桶+链表的思想。

接着看下createMap

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

实现思路也很简单就是给线程对象的threadLocals实例化了一个ThreadLocalMap实例,将threadLocal对象作为key将存储值

get

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();
}

首先也是获取到当前线程对象,然后根据线程对象获取ThreadLocalMap实例,如果map实例不等于空就利用mapgetEntry方法获取到值否则调用setInitialValue()

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
    if (this instanceof TerminatingThreadLocal) {
        TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
    }
    return value;
}

也是利用initialValue获取到初始值,然后如果没有map就创建map,如果有了就把当前threadLocal作为key传入进去,默认实现是返回null,当然如果是利用withInitial来创建的ThreadLocal实例因为initialValue方法已经被SupplierThreadLocal给重写了,所以会返回你传入的默认值。

remove

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
        m.remove(this);
    }
}

根据当前线程对象获取到ThreadLocalMap然后判断是否为空,如果为空就什么也不做否则就调用ThreadLocalMapremove方法对其进行删除

InheritableThreadLocal 是什么

有的时候你想给线程设置的局部变量可以使得在当前线程下开启的子线程也能够有一份副本传递过去,而ThreadLocal是不具备传递性是对某条线程设置局部变量, 那么有没有什么办法能够使将父线程的线程局部变量传递一份副本到子线程上呢?答案是有的,就是InheritableThreadLocal

InheritableThreadLocal 的使用

先看下InheritableThreadLocal是如何使用的

public class InheritableThreadLocalTest {
    static InheritableThreadLocal<String> message = new InheritableThreadLocal<>();
    public static void main(String[] args) {
        message.set("Main Thread Message");
        Thread threadA = new Thread(() -> {
            System.out.println(message.get());
        });
        threadA.start();
    }
}

控制台输出:

Main Thread Message

它的用法和ThreadLocal差不多但其身所包含的含义不同

InheritableThreadLocal 的原理

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    ....
}

从源码上看InheritableThreadLocal就是继承至ThreadLocal其本身并没有对外再定义公共方法,但其重写了父类的三个方法

protected T childValue(T parentValue){
    ...
}
ThreadLocalMap getMap(Thread t) {
    ...
}

void createMap(Thread t, T firstValue){
    ...
}

别的都还是利用ThreadLocal的实现

childValue

protected T childValue(T parentValue) {
    return parentValue;
}

将传进来的值返回出去,这个方法的目的是为了重写而重写因为其父类的默认实现是抛出一个UnsupportedOperationException异常,它的意义在于如果你需要其它的处理那么重写该方法就好。

getMap

ThreadLocalMap getMap(Thread t) {
    return t.inheritableThreadLocals;
}

从源码中可以了解到返回的是线程对象中的inheritableThreadLocal实例和父类的实现返回的map不同

 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

它实际上也是一个ThreadLocalMap

createMap

void createMap(Thread t, T firstValue) {
    t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}

创建一个ThreadLocalMap实例赋值给线程对象的inheritableThreadLocals

如何实现的线程局部变量传递?

这个疑问貌似在重写的这几个点并没有解决?那它是怎么做到的?答案在创建线程的构造函数中,代码比较长我仅截取片段

if (inheritThreadLocals && parent.inheritableThreadLocals != null)
    this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

根据代码,先观其思路,大体思路如下:判断从构造函数传入的inheritThreadLocals是否为true,然后再判断父线程中的inheritableThreadLocal不为空,如果不为空则调用ThreadLocalcreateInheritedMap并传入父线程的inheritableThreadLocals创建一个ThreadLocalMap

在深入的剖析一下

parent父线程是如何获取的?

 Thread parent = currentThread();

在构造函数中调用currentThread来获取当前正在实例化这个线程对象的线程,就得到了其父线程,如何在来看createInheritedMap

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];

    for (Entry e : parentTable) {
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

原理很简单,实际上就是拿到父类的inheritableThreadLocals然后一个个的放到子线程的inhertiableThreadLocals中去

结论

实际上ThreadLocal就是一层壳,具体的操作都还是在Thread中,然后再利用传入的ThreadLocal实例做为key值的方式去在线程中的map查找或赋值。

月泉
伪文艺中二青年,热爱技术,热爱生活。