Skip to content

ThreadLocal介绍

一、ThreadLocal 概述

众所周知,ThreadLocal不存在线程安全问题,那是因为ThreadLocal维护变量的时候,它为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个该变量,这样同时多个线程访问该变量并不会彼此相互影响,因此他们使用的都是自己从内存中拷贝过来的变量的副本。

Thread 在内部是通过ThreadLocalMap来维护ThreadLocal变量表, 在Thread类中有一个threadLocals 变量,是ThreadLocalMap类型的,它就是为每一个线程来存储自身的ThreadLocal变量的, ThreadLocalMap是ThreadLocal类的一个内部类,这个Map里面的最小的存储单位是一个Entry, 它使用ThreadLocal作为key, 变量作为 value,这是因为在每一个线程里面,可能存在着多个ThreadLocal变量。

1.1 源码剖析

讲解主要代码过程

1.1.1 set(T value)

public void set(T value) {
	//获取当前线程
    Thread t = Thread.currentThread();
    //根据当前线程获取线程变量,threadlocal的是threadLocals变量
    ThreadLocalMap map = getMap(t);
    //为空创建map,不为空设置值
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}

1.1.2 get()

public T get() {
	//获取当前线程
    Thread t = Thread.currentThread();
    //根据当前线程获取线程变量,threadlocal的是threadLocals变量
    ThreadLocalMap map = getMap(t);
    //如果当前map不为空的情况
    if (map != null) {
    	//获取当前线程的节点数据并且返回
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //如果map为空的情况下,初始化这个线程的map,value设为null值并返回
    return setInitialValue();
}

1.1.3 remove()

public void remove() {
	//获取map,移除变量
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
        m.remove(this);
    }
}

1.2 ThreadLocalMap

ThreadLocalMap不是普通的Map而是一个定制Map,这个Map使用了使用“开放寻址法”中最简单的“线性探测法”解决散列冲突问题。这点是和我们平常使用的普通的HashMap不一样的。其中还是用了一个神奇的数字HASH_INCREMENT= 0x61c88647,保证了一个完美的散列分布。

1.4 优点

由于每个线程里面的变量都是隔离的,避免了线程安全的问题。

1.5 缺点

父子线程中就有了局限性,如果子线程拿不到父线程的中的ThreadLocal值。

二、InheritableThreadLocal(ITL)

ThreadLocal由于父子线程之间存在局限性,JD提供了实现方案:InheritableThreadLocal,这里需要注意的是InheritableThreadLocal源码并没有提供太多的方法,只是重写了三个方法。原本ThreadLocal存在Thread里的变量为threadLocals改成了使用inheritableThreadLocals。那么只重写了几个方法的情况下如何实现父线程向子线程传递呢。重点内容在这里:

Thread类的构造方法:

 //当前线程不为空并且当前线程的inheritableThreadLocals不为空的情况下,将当前线程的inheritableThreadLocals传递到new的线程里,这里就实现了变量复制
 if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

InheritableThreadLocal对于整个ThreadLocal的操作是不变的。

2.1 优点

InheritableThreadLocal优点在于父线程向子线程传递了属于自己的线程的变量

2.2 缺点

InheritableThreadLocal只能对通过手动新建的线程有效,对于当前使用火热的线程池是无效的。

三、TransmittableThreadLocal(TTL)

首先介绍以下这个类不是jdk自带的类,是阿里的库,其次TransmittableThreadLocal是为了解决InheritableThreadLocal对于线程池无效的情况而产生的。

我们在使用线程的时候往往不会只是简单的new Thrad对象,而是使用线程池。TTL的做法也比较直接,使用了装饰器模式,既然InheritableThreadLocal只是在线程Create的时候复制一份父线程数据,那么为了支持线程池就需要在Thread的run方法之前把父线程的数据copy一下就可以了。

ttl如何保证在线程池里实现父子线程之间的共享:

继承Runable,重写runable方法,在执行run方法之前copy一份线程变量

public final class TtlRunnable implements Runnable {
    private final AtomicReference<Map<TransmittableThreadLocal<?>, Object>> copiedRef;
    private final Runnable runnable;
    private final boolean releaseTtlValueReferenceAfterRun;

    private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
    //从父类copy值到本类当中
        this.copiedRef = new AtomicReference<Map<TransmittableThreadLocal<?>, Object>>(TransmittableThreadLocal.copy());
        this.runnable = runnable;//提交的runable,被修饰对象
        this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
    }
    /**
     * wrap method {@link Runnable#run()}.
     */
    @Override
    public void run() {
        Map<TransmittableThreadLocal<?>, Object> copied = copiedRef.get();
        if (copied == null || releaseTtlValueReferenceAfterRun && !copiedRef.compareAndSet(copied, null)) {
            throw new IllegalStateException("TTL value reference is released after run!");
        }
        //装载到当前线程
        Map<TransmittableThreadLocal<?>, Object> backup = TransmittableThreadLocal.backupAndSetToCopied(copied);
        try {
            runnable.run();//执行提交的task
        } finally {
        //clear
            TransmittableThreadLocal.restoreBackup(backup);
        }
    }
}

这是官方的流程图TransmittableThreadLocal-sequence-diagram