java并发编程第四天---ThreadLocal使用及源码分析

460 阅读5分钟

简介

多线程访问同一个共享变量时特别容易出现并发问题,为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步,同步的措施一般是加锁,但加锁会影响性能。

那么有没有一种方式可以做到,当创建变量后每个线程对其进行访问的时候,访问的是自己线程的变量呢? ThreadLocal 就可以做这件事。

创建 ThreadLocal 变量后,每个线程都会复制该变量到自己的本地内存,当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。


使用

public class ThreadLocalTest {

    // 创建 ThreadLocal 变量
    static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    // 打印函数
    static void print(String name) {
        System.out.println(name + ":" + threadLocal.get());
        // 清除当前线程中本地变量 threadLocal 的值
        threadLocal.remove();
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            // 设置线程 t1 本地变量 threadLocal 的值
            threadLocal.set("8:45");
            print("t1");
            // 清除后打印本地变量 threadLocal 的值
            System.out.println("after remove t1:"+threadLocal.get());
        });

        Thread t2 = new Thread(() -> {
            // 设置线程 t2 本地变量 threadLocal 的值
            threadLocal.set("2020-5-28");
            print("t2");
            // 清除后打印本地变量 threadLocal 的值
            System.out.println("after remove t2:"+threadLocal.get());
        });

        t1.start();
        t2.start();
    }
}

当线程 t1 、 t2 访问 threadLocal 变量时,访问的都是线程的本地内存中的值,不会发生资源冲突,也就没有线程安全问题。


源码分析

Thread 类中有两个变量,都是 ThreadLocalMap 类型的变量,每个线程的 ThreadLocal 型变量都存在 threadLocals 里面。很显然,Map 结构表示每个线程能关联多个 ThreadLocal 类型的变量。

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

我们来看看 ThreadLocal 类的工作原理。

public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    
    // 获取当前线程的 threadLocals 变量
    ThreadLocalMap map = getMap(t);
    // 如果 threadLocals 变量不空,则直接设值
    if (map != null)
        map.set(this, value);
    else
        // 如果 threadLocals 变量未空,则初始化
        createMap(t, value);
}

// 获取线程 t 的 threadLocals
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// 初始化 线程 t 的 threadLocals
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap 是一个 HashMap 结构,key 是当前 ThreadLocal 的实例对象引用,value 则是通过 set 方法设的值,Hhaspmap 的具体源码就不分析了。

public T get() {
    Thread t = Thread.currentThread();
    // 获取当前线程的 threadLocals 变量
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 以当前 ThreadLocal 的实例对象引用作为 key
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            // 返回 key 对应的 value
            T result = (T)e.value;
            return result;
        }
    }
    // 如果当前线程 threadLocals 变量未空,则先初始化
    return setInitialValue();
}
public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null)
         // 以当前 ThreadLocal 的实例对象引用作为 key
         m.remove(this);
 }

总结:

每个线程都有 threadLocals 成员变量,该变量类型为 HashMap,其中 key 为我们定义的 ThreadLocal 变量的 this 引用, value 为我们使用 set 方法设的值。每个线程的 ThreadLocal 型变量都存放在线程自己的 threadLocals 变量中。


ThreadLocal 没有继承性

public class ThreadLocalTest {
    static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        threadLocal.set("顺溜");

        Thread t1 = new Thread(()->{
            System.out.println("t1:" + threadLocal.get());
        });

        t1.start();
        
        System.out.println("main:"+threadLocal.get());
    }
}

同一个 ThreadLocal 型变量在父线程中被设置值后,在子线程中是获取不到的。这是正常现象,因为父线程 ( main ) 和 子线程 ( t1 ) 是两个不同的线程。那么有没有办法让子线程能访问到父线程中设置的值? 答案是有。


InheritableThreadLocal

InheritableThreadLocal 继承自 ThreadLocal ,其提供了一个特性,是让子线程可以访问在父线程中设置的本地变量。


使用

public class ThreadLocalTest {
    static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        threadLocal.set("顺溜");

        Thread t1 = new Thread(()->{
            System.out.println("t1:" + threadLocal.get());
        });

        t1.start();

        System.out.println("main:"+threadLocal.get());
    }
}


源码分析

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

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

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

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

InheritableThreadLocal 重写了三个方法,在 InheritableThreadLocal 的世界里,变量 inheritableThreadLocals 取代了 threadLocals。

InheritableThreadLocal 是如何让子线程可以访问父线程的本地变量呢?这得从 Thread 类的构造说起。

public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
    init(group, target, name, stackSize);
}

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
  
	...
    
    // 获取当前线程(父线程)
    Thread parent = currentThread();
    
       
	...
  
    // inheritThreadLocals默认为true, 如果父线程的 inheritableThreadLocals 不为 null 
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        //  设置子线程中的 inheritableThreadLocals 交量
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    ...
}

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

// 从给定的 parentMap 中构造出一个新的 Map,只在 createInheritedMap 方法中调用。
// 十分类似 HashMap 的带参构造(思路一模一样),这里就不分析源码了。
private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];

    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        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++;
            }
        }
    }
}

总结

InheritableThreadLocal 类通过重写 getMapcreateMap 方法,让本地变量保存到了具体线程的 inheritableThreadLocals 变量里面。当父线程创建子线程时,构造函数会把父线程中的 inheritableThreadLocals 变量复制一份保存到子线程的 inheritableThreadLocals 变量中。

那么在什么情况下需要子线程可以获取父线程的 threadlocals 变量呢?情况还是蛮多的,比如子线程需要使用存放在父线程 threadlocals 中的用户登录信息,再比如一些中间件,需要把统一的 id 追踪 的整个调用链路记录下来。这些情况下InheritableThreadLocal 就显得很有用