ThreadLocal 理解.md 6.6 KB

ThreadLocal叫做线程变量,该变量在每个线程中都创建了一个副本,每个线程都只能访问自己内部的副本变量,即该变量对其他线程而言是隔离的。

源码里是这样注释说明的:

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 *
 * <p>For example, the class below generates unique identifiers local to each
 * thread.
 * A thread's id is assigned the first time it invokes {@code ThreadId.get()}
 * and remains unchanged on subsequent calls.
 * 
 * ...
 *
 * @author  Josh Bloch and Doug Lea
 * @since   1.2
 */
public class ThreadLocal<T> {
    //...
}

简单的例子认识ThreadLocal

/** 线程变量 */  
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();  
  
public static void main(String[] args) {  
    new Thread(() -> {  
        threadLocal.set("A");  
        System.out.println("线程A的线程变量值:" + threadLocal.get());  
    }).start();  
  
    new Thread(() -> {  
        try {  
            TimeUnit.SECONDS.sleep(1);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("线程B的线程变量值(before set):" + threadLocal.get());  
        threadLocal.set("B");  
        System.out.println("线程B的线程变量值(after set):" + threadLocal.get());  
    }).start();  
}

输出结果:

线程A的线程变量值:A
线程B的线程变量值(before set):null
线程B的线程变量值(after set):B

可见同一个变量在线程A和线程B的值并不相同

ThreadLocal#set 实现原理

我们来看一下 threadLocal.set("A") 实现过程

    /**
     * 
     * 获取当前线程内部的map对象,将值set进去
     */
    public void set(T value) {
    	//获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的成员变量ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //如果map不为空,将值设置到map中,key是this,即threadLocal对象
            map.set(this, value);
        else
            //给线程threadLocals变量new一个ThreadLocalMap对象,并添加第一个键值对
            createMap(t, value);
    }
    ......
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     * 创建一个ThreadLocalMap对象覆盖掉线程内部已有的threadLocals,并设置值
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

很明显,threadLocal.set("A") 实际上就是向当前线程内部变量 threadLocals 设置值

顺藤摸瓜我们再来看看 ThreadLocalMap 是个什么类?

/**
 * ThreadLocalMap is a customized hash map suitable only for
 * maintaining thread local values. No operations are exported
 * outside of the ThreadLocal class. The class is package private to
 * allow declaration of fields in class Thread.  To help deal with
 * very large and long-lived usages, the hash table entries use
 * WeakReferences for keys. However, since reference queues are not
 * used, stale entries are guaranteed to be removed only when
 * the table starts running out of space.
 */
static class ThreadLocalMap {  
  ....
}

原来ThreadLocalMap 是 ThreadLocal 的内部类,是一个自定义 hash map,只适用于线程局部变量。可以把它就看做是一个 HashMap。

顺藤又摸一个瓜,看下 t.threadLocals 是什么鬼?这次是在 Thread 类内部

public
class Thread implements Runnable {
    ...
	// line 179
	/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ...
}

threadLocals 只是线程类Thread的一个成员变量,默认null

ThreadLocal#get 实现原理

	...
	/**
	 * 初始值null,在ThreadLocal实例化时可以覆盖该方法
	 */ 
	protected T initialValue() {  
	    return null;  
	}
	...
	/**
	 * 从当前线程内部map获取变量值
	 */
    public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的成员变量ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //根据threadLocal对象从map中获取Entry对象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                //获取保存的数据
                T result = (T)e.value;
                return result;
            }
        }
        //初始化数据
        return setInitialValue();
    }

	/**
	 * 设置初始值,如果当前线程threadLocals为空,则创建一个ThreadLocalMap实例
	 */
	private T setInitialValue() {  
    	//获取要初始化的数据
	    T value = initialValue();  
	    //获取当前线程
	    Thread t = Thread.currentThread();  
	    //获取当前线程的成员变量ThreadLocalMap对象
	    ThreadLocalMap map = getMap(t);  
	    if (map != null)  
    	    //如果map不为空,将初始值设置到map中,key是this,即threadLocal对象,value是初始值
	        map.set(this, value);  
	    else   
    	    //如果map为空,则需要创建新的map对象     
		    createMap(t, value);  
	    return value;  
	}

非常简单,就是从线程类Thread的成员变量threadLocals中获取变量值。

总结

ThreadLocal 和 Synchronized 都是为了解决多线程中相同变量的访问冲突(线程安全)问题,不同的是:

  • Synchronized 是通过线程等待,牺牲时间来解决访问冲突
  • ThreadLocal 是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal 具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。

正因为 ThreadLocal 的线程隔离特性,使他的应用场景相对来说更为特殊一些。

当某些数据是 以线程为作用域并且不同线程具有不同的数据副本 的时候,就可以考虑采用ThreadLocal。

比如:在多数据源切换时,就使用了 ThreadLocal 来设置当前使用的数据源 191202-Spirng动态多数据源