ThreadLocal,InheritableThreadLocal,TransmittableThreadLocal应用场景及解析

幸越泽
2023-12-01

本文分为两部分,上部分为使用方式,下部分为解析

1.使用ThreadLocal

// 大家都知道在Tl(ThreadLocal)中 数据可以在Thread中共享
// 那么以下场景会存在问题
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
threadLocal.set(1);

new Thread(new Runnable() {
  @Override
  public void run() {
    System.out.println(threadLocal.get()); //输出null 因为开启了新的线程
  }
}).start();

2.使用InheritableThreadLocal

// 这时使用InheritableThreadLocal (ThreadLocal的子类) 可以解决上述问题
ThreadLocal<Integer>  InheritableThreadLocal = new InheritableThreadLocal<>();
InheritableThreadLocal.set(1);
new Thread(new Runnable() {
  @Override
  public void run() {
    System.out.println(InheritableThreadLocal.get()); //输出1
  }
}).start();

3.InheritableThreadLocal在线程池场景中存在的问题

//  但是我们实际使用中,不可能每次使用都new Thread,一般都是使用线程池,这时就会存在新的问题
ThreadLocal<Integer>  InheritableThreadLocal2 = new InheritableThreadLocal<>();

// 线程池size设置为1是为了模拟线程池中的线程都已经被初始化过
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,1, 1L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));
threadPoolExecutor.prestartAllCoreThreads();  //预先启动所有核心线程(模拟线程池中的线程都已经被初始化过,这时候又有任务提交过来)
InheritableThreadLocal2.set(2);  // 为inInheritableThreadLocal设置值
threadPoolExecutor.execute(new Runnable() {
  @Override
  public void run() {
    System.out.println(InheritableThreadLocal2.get());  //输出null
  }

4.使用TTL解决问题

TTL为阿里开源框架。

// 如何解决上面的问题,可以使用TTL  TransmittableThreadLocal(可传递线程本地变量) 继承自InheritableThreadLocal,所以他拥有继承自InheritableThreadLocal的所有特性
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,1, 1L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));
threadPoolExecutor.prestartAllCoreThreads();//预先启动所有核心线程(模拟线程池中的线程都已经被初始化过,这时候又有任务提交过来)
TransmittableThreadLocal<Integer> transmittableThreadLocal = new TransmittableThreadLocal<Integer>();
transmittableThreadLocal.set(2);
// 注意。这里使用了TtlRunnable,  TransmittableThreadLocal一定要搭配TTlRunnable或者TtlCallable
threadPoolExecutor.execute(TtlRunnable.get(new Runnable() {
  @Override
  public void run() {
    System.out.println(transmittableThreadLocal.get());  //输出2
  }
}));

源码

前置了解:

//在ThreadLocal中,存在一个ThreadLocalMap.

ThreadLocal.ThreadLocalMap threadLocals = null;


// ThreadLocalMap 的组成也非常简单,我们重点关注两个地方 一个是内部类Entry
// 这个Entry是弱引用的,可以理解为: 如果这个Entry只在传入的ThreadLocal中引用它,那么会被回收。
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
//另一个关注点是ThreadLocalMap的参数一个数组Entry
// 它是用来干啥的呢,通过ThreadLocal 的hash 取模size 计算出来数组的下标,存入对应的ThreadLocal及Threadlocal的值 
// 简单来说就是你在调用ThreadLcaol.set时,会把ThreadLocal和对应的值转换为一个Entry,放到数组中。
private Entry[] table;

1.ThreadLocal为何可以在Thread中进行传递

set方法:

//ThreadLocal的set方法
public void set(T value) {
    Thread t = Thread.currentThread();   //获取当前线程
    //线程中维护了一个ThreadLocalMap ,这个getMap方法非常的简单。直接就是  return t.threadLocals;
    ThreadLocalMap map = getMap(t);  
    if (map != null)
        map.set(this, value);   //如果线程中已经存在ThreadLocalMap,转换成Entity放到对应的数组中(上面的Entity数组)
    else
        createMap(t, value);   //线程中不存在ThreadLocalMap,为线程创建一个ThreadLocalMap,并把当前ThreadLocal和值设置为first
}


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

get方法:

// ThreadLocal get方法
public T get() {
    Thread t = Thread.currentThread();  //获取当前线程
    ThreadLocalMap map = getMap(t);    //获取线程ThreaLocalMap
    if (map != null) {
        // 通过当前的ThreadLocal的hashCode与线程中entry数组size取模计算出来数组下标,拿到对应Entry
        ThreadLocalMap.Entry e = map.getEntry(this);    
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;   
            return result;
        }
    }
    return setInitialValue();  // ThreadLocal是可以直接设置默认值的,如果没有ThreadLocalMap(没有调用过set方法) 会直接返回默认值(默认null,可以重写initialValue方法))
}

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

以上解释了为什么ThreadLocal可以在线程中传递,因为他在线程中维护了一个ThreadLocalMap

2.为何InheritableThreadLocal可以在子线程中传递

//重点!其实Thread中维护了两个ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals = null;  //Threadlocal使用
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;  //InheritableThreadLocal使用

// 这个是Thread的init方法,在new一个线程的时候,会调用init方法
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
                      
 // 我们重点关注这两段代码,获取当前线程(其实就是获取被new线程的父线程)
    Thread parent = currentThread();
    // 如果父线程拥有inheritableThreadLocals值,会把他设置到子线程的inheritableThreadLocals中
    if (inheritThreadLocals && parent.inheritableThreadLocals != null){
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
}

这时,子线程也拥有了通过inheritableThreadLocal中设置的变量但是是维护到inheritableThreadLocals(线程中的ThreadLocalMap)并不是维护到threadLocals(线程中的ThreadLocalMap)中

接下来我们看一下inheritThreadLocal获取数据的过程及它的特性;

get方法:

//与ThreadLocal的get方法一样的。

public T get() {
    Thread t = Thread.currentThread();
    // 区别点这边getMap就不在是获取t.threadLocals了。而是重写了getMap获取的inheritableThreadLocals
    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 getMap(Thread t) {
   return t.inheritableThreadLocals;
}

set方法:

//同样的跟ThreadLocal的set一样。只是重写了几个方法
public void set(T value) {
    Thread t = Thread.currentThread();
    // 跟get一样获取的是inheritableThreadLocals
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
    // 这个方法也被重写。创建的就是inheritableThreadLocals 不在是threadLocals (这俩都是ThreadLocalMap)
        createMap(t, value);
}

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

以上就可以解释为何inheritableThreadLocal可以在子线程中传递,因为init线程的时候就会把数据存入单独的一个ThreadLocalMap中

但是inheritableThreadLocal存在问题,即只能在创建线程的时候调用init方法才可以拿到数据,如果是线程池场景,线程早就被创建好了。拿不到inheritableThreadLocal设置的值,这个时候可以使用TTL

3.TTL是如何解决线程复用可以拿到ThreadLocal值的

先从TransmittableThreadLocal入手,它是一个继承了inheritableThreadLocal的类,所以它天然就可以在子线程中传递(在线程创建之前),接下来是它的一些特性:


// 全文重点!!!
// TransmittableThreadLocal 中维护了一个holder,注意是static类型的InheritableThreadLocal!
// 本质上数据还是维护在当前线路的ThreadLocalMap.Entry中
// 会把所有调用过set方法的TransmittableThreadLocal放到map里面
// 使用的是WeakHashMap
private static InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder =
        new InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() {
            @Override
            protected Map<TransmittableThreadLocal<?>, ?> initialValue() {
                return new WeakHashMap<TransmittableThreadLocal<?>, Object>();
            }

            @Override
            protected Map<TransmittableThreadLocal<?>, ?> childValue(Map<TransmittableThreadLocal<?>, ?> parentValue) {
                return new WeakHashMap<TransmittableThreadLocal<?>, Object>(parentValue);
            }
        };
        
        
        

// TransmittableThreadLocal 的set方法
public final void set(T value) {
    super.set(value);
    if (null == value) { // may set null to remove value
        removeValue();
    } else {
        // 特殊一点的是这个addValue
        // 会往hodler的map中插入当前的TransmittableThreadLocal作为key
        addValue();
    }
}



private void addValue() {
    if (!holder.get().containsKey(this)) {
        holder.get().put(this, null); 
    }
}

接下来我们看看与TransmittableThreadLocal搭配使用的TTLRunnable


// TTLRunnable中的两个参数
// 在创建TTLRunable的时候,会把所有TransmittableThreadLocal的数据保存在copiedRef中
private final AtomicReference<Object> copiedRef;
//包装runnable
private final Runnable runnable;


//重点关注构造方法与装饰的run方法(私有构造, 使用TtlRunnable.get()方法进行创建实例)
private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
    // 1. Transmitter.capture方法  通过TransmittableThreadLocal上维护的holder获取当前线程(父线程)所有的TransmittableThreadLocal与数据
    // 2. 返回的是一个Map<TransmittableThreadLocal<?>, Object> 
    // 3. key为所有设置的TransmittableThreadLocal,value为TransmittableThreadLocal对应设置的value
    // 4. 这里的操作都是在父线程中进行的,所以获取的值也是在父线程中Set的值。如果在子线程中set成了其它值(这时就会放到子线程的ThreadLocalMap中)。是不会影响到其它线程的数据的
    // 简单来说就是获取所有TransmittableThreadLocal并把他们的值放到map中返回
    this.copiedRef = new AtomicReference<Object>(Transmitter.capture());
    
    this.runnable = runnable;
}


//包装了run方法
public void run() {
    
    // 获取创建TTLRunable时设置的copieRef
    Object copied = copiedRef.get();
    
    if (copied == null || releaseTtlValueReferenceAfterRun && !copiedRef.compareAndSet(copied, null)) {
        throw new IllegalStateException("TTL value reference is released after run!");
    }
   
   //  Transmitter.replay这里做了几个事情 
    // 1.备份一下现在holder中的所有数据(就是返回的backUp)
    // 2.将copiedRef(父线程中获取到的数据)与当前holder中的数据进行对比,如果holder中有多余的数据,则remove掉
    // 3.将copiedRef中的数据放到当前线程的ThreadLocalMap中(存的是TransmittableThreadLocal,然后调用了TransmittableThreadLocal的set方法,就是ThreadLocal的Set,这里要注意,因为TransmittableThreadLocal本质是一个InheritableThreadLocal所以会放到名字为inheritableThreadLocals的ThreadLocalMap中)

            Object backup = Transmitter.replay(copied);
    try {
        // 这时。在线程中就可以直接通过TTL获取到对应数据,
        runnable.run();
    } finally {
        // 恢复之前备份的数据,这时可能在run时又设置了新的TransmittableThreadLocal到holder中,对backup进行对比,将holder中多余的数据删除
        // 把bacuUp恢复到threadLocalMap中
        Transmitter.restore(backup);
    }
} 


//这个方法就是在创建TTLRunable时调用的Transmitter.capture
public static Object capture() {
    Map<TransmittableThreadLocal<?>, Object> captured = new HashMap<TransmittableThreadLocal<?>, Object>();
    for (TransmittableThreadLocal<?> threadLocal : holder.get().keySet()) {
        captured.put(threadLocal, threadLocal.copyValue());
    }
    return captured;
}

需要注意的是。 在创建TTLRunnable时,是在父线程中创建的,但是run方法是子线程执行的。

其中,多个子线程混用TransmittableThreadLocal也不会存在问题,因为是线程隔离的,并且数据都是来源于父线程。

 类似资料: