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> {
//...
}
/** 线程变量 */
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("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
...
/**
* 初始值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 具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。正因为 ThreadLocal 的线程隔离特性,使他的应用场景相对来说更为特殊一些。
当某些数据是 以线程为作用域并且不同线程具有不同的数据副本 的时候,就可以考虑采用ThreadLocal。
比如:在多数据源切换时,就使用了 ThreadLocal 来设置当前使用的数据源 191202-Spirng动态多数据源