一、基本概念
1.1 ThreadLocal 的用途
首先,我们来看一下JDK
源码中对于ThreadLocal
的解释:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one has its own, independently initialized copy of the variable. ThreadLocal instances are typically privatestatic fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
翻译过来就是:
ThreadLocal
用来提供线程内的局部变量。这些变量在多线程环境下访问时能够保证各个线程里的变量相对独立于其它线程内的变量,ThreadLocal
实例通常来说都是private static
类型的。
因此,ThreadLocal
适用于满足下面条件的场景:
- 每个线程 有且仅有 该对象的一个实例
- 在该线程的整个生命周期内 有多处用到 该实例
- 存在 多线程访问 的情况
1.2 ThreadLocal 的使用
ThreadLocal
的API
很简单,它包含以下四个签名:
-
get
:获取ThreadLocal
中当前线程共享变量的值。 -
set
:设置ThreadLocal
中当前线程共享变量的值。 -
remove
:移除ThreadLocal
中当前线程共享变量的值。 -
initialValue
:ThreadLocal
没有被当前线程赋值时或当前线程刚调用remove
方法后调用get
方法,返回此方法值。
我们用下面的一小段例子,来熟悉一下ThreadLocal
的使用。
class ThreadLocalSamples {
private static ThreadLocal<Integer> sThreadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 5;
}
};
static void startSample() {
for (int i = 0; i < 3; i++) {
new SampleThread("thread_" + i).start();
}
}
private static class SampleThread extends Thread {
private String mThreadName;
SampleThread(String threadName) {
mThreadName = threadName;
}
@Override
public void run() {
for (int j = 0; j < 5; j++) {
try {
long sleep = (long) (Math.random() * 50);
Thread.sleep(sleep);
int result = sThreadLocal.get();
sThreadLocal.set(++result);
Log.d("ThreadLocalSamples", "ThreadName=" + mThreadName + ",result=" + result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
从打印的结果可以看到,虽然这
3
个线程访问是同一个ThreadLocal
实例,但是它们通过ThreadLocal
的get/set
方法读写的并不是同一个实例,所以保证了在多线程环境下的独立性。
二、源码
2.1 源码实现
为了加深对于ThreadLocal
的理解,我们来分析一下它的内部实现。ThreadLocal
设计的核心思想就是:每一个Thread
维护一个ThreadLocalMap
,ThreadLocalMap
的key
是ThreadLocal
,而value
就是真正要存储的Object
。这种方案设计的优点是:
- 每个
Map
的Entry
数量变小了,之前是Thread
的数量,现在是ThreadLocal
的数量,能提高性能。 - 当
Thread
销毁之后对应的ThreadLocalMap
也就随之销毁了,能减少内存使用量。
我们先来看一下set
和get
的主要流程:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
写入的流程为:
- 通过静态方法
currentThread
获取当前执行指令的线程。 - 得到该线程的私有成员变量
threadLocals
,其类型为ThreadLocalMap
,如果没有创建那么就先创建。 - 通过
ThreadLocalMap
的set
方法存入实际的Object
,其key
值为ThreadLocal
实例。
读取的流程为:
- 通过静态方法
currentThread
获取当前执行指令的线程,然后获取和该线程关联的ThreadLocalMap
- 以
ThreadLocal
实例为key
值,通过ThreadLocalMap
的getEntry
方法找到Object
,如果找到就直接返回;如果没有找到就调用setInitialValue
方法,该方法会调用到我们重写的initialValue
来尝试获取一个初始值。
总结下来就是:ThreadLocal
将一个共用的ThreadLocal
静态实例作为key
,将不同对象的引用保存到不同线程的ThreadLocalMap
中,然后在线程执行的各处通过这个静态ThreadLocal
实例的get()
方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
它之所以可保证 多线程环境下的相互独立,原因在于:每个线程中都有一个自己的ThreadLocalMap
类对象,可以将线程自己的对象保持到其中,线程可以正确的访问到自己的对象。
当然,这种 独立性必须要基于一个前提:通过set
方法存储的对象并不是多个线程共享的。如果是共享的,那么多个线程get
出来的是同一个是实例,仍然会存在多线程问题。
2.2 ThreadLocalMap
ThreadLocalMap
是ThreadLocal
中的一个内部类,与HashMap
类似,它也会遇到Hash
冲突的问题,HashMap
采用了 链地址法 解决冲突,而ThreadLocalMap
则采用 开放寻址法 解决冲突。
关于ThreadLocalMap
还有一个疑问,就是它有可能会出现内存泄漏,原因是:ThreadLocalMap
的key
值保存的是ThreadLocal
的弱引用,假如ThreadLocal
被回收,那么就会无法通过Key
找到Object
,假如线程一直没有结束,那么这些Object
就永远不会被回收。
在ThreadLocalMap
内部对于这种情况做了优化,就是在getEntry
和set
方法查找存储位置的时候,如果发现了key
为null
的槽,那么会将这些槽中对应的Object
引用置为null
。这并不能解决所有问题,对于使用者来说,可以做额外的两项优化操作:
- 手动调用
ThreadLocal
的remove
函数,删除不再需要的ThreadLocal
- 将
ThreadLocal
声明为private static
的,使得ThreadLocal
的生命周期更长。
参考文献
(1) 正确理解 ThreadLocal
(2) 深入剖析 ThreadLocal 实现原理以及内存泄漏问题
(3) ThreadLocal 和 synchronized 的区别
网友评论