原文作者:腾哥红安
原文地址:原文链接
摘抄申明:我们不占有不侵权,我们只是好文的搬运工!转发请带上原文申明。
ThreadLoacal 理解以及源码分析
问题
- 什么是ThreadLocal ?
- ThreadLocal的目的和作用 ?
- ThreadLocal原理 ?
- ThreadLocal使用实例 ?
- ThreadLocal使用场景 ?
ThreadLocal介绍
线程内局部变量。用于实现线程内数据共享,即对于一个相同的代码模块,每个线程访问的时候代码模块内的变量互补干扰,互不影响。
ThreadLocal目的
ThreadLocal的主要目的是为了解决变量在单个线程内部变量的传递问题,ThreadLocal修饰的变量多个线程之间不共享,所以不存在安全性问题,因为数据都不共享,所以ThreadLocal不是为了解决多线程之间的安全问题。
ThreadLocal作用
ThreadLocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同,而同一个线程在任何时候访问这个本地变量的结果都是一致的。当此线程结束生命周期时,所有的线程本地实例都会被GC。ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定。 Thread Local的作用于是线程内部,伴随线程执行始终,线程结束,变量生命结束。
ThreadLocal源码分析
ThreadLocal 定义了4个方法:
- get():返回此线程局部变量的当前线程副本中的值。
- initialValue():返回此线程局部变量的当前线程的“初始值”。
- remove():移除此线程局部变量当前线程的值。
- set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。
ThreadLocal的核心是通过静态内部类
ThreadLocalMap
来处理数据的,我们先看下ThreadLocalMap的源码。
static class ThreadLocalMap {
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
}
Entry是ThreadLocalMap
核心存储key-value的。继承WeakReference
,Entry所对应key(ThreadLocal实例)的引用为一个弱引用 。
看一下set方法:
private void set(ThreadLocal> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
//根据散列计算出元素在数组中的位置。
int i = key.threadLocalHashCode & (len-1);
//采用线性探测法寻找合适的位置
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal> k = e.get();
//如何存在key重复,直接覆盖
if (k == key) {
e.value = value;
return;
}
//如果key为空,但是value是有值的
if (k == null) {
//用新元素替代老旧的元素的值
replaceStaleEntry(key, value, i);
return;
}
}
//如果key不重复,并且也不为空,则实例化一个Entry
tab[i] = new Entry(key, value);
int sz = ++size;
//清除陈旧的Entry,如果数组的长度大于阈值,及 len * 2 / 3,则进行rehash();
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
有心的朋友可能会发现,这边的是采用线性探测法
,而HashMap采用的是拉链法
。
set()除了存储数据之外,还清楚了key为空的数据,以及清除没有使用的实体。
再看下getEntry()
private Entry getEntry(ThreadLocal> key) {
//根据散列计算出元素在数组中的位置。
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//key存在则直接返回Entry。
if (e != null && e.get() == key)
return e;
//因为是使用开放定址法,所以当前的key的散列值和元素的索引并不是完全对应的
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal> k = e.get();
if (k == key)
return e;
// 当key为空的时候,处理key为空的Entry,有利于GC的回收,有效避免内存泄漏
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
好了,现在再回头看ThreadLocal的几个方法
get():返回当前线程对应的线程变量的值
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//通过当前线程获取当前的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) {
//通过key获取实体Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果map为空,则创建一个新的Map ,并返回空值
return setInitialValue();
}
set(T value):设置当前线程的局部变量值
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//通过当前线程获取当前的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//如果map存在则直接设置value,不存在则创建map并设置value。
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
remove():将当前线程局部变量的值删除。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
此方法目的是减少内存的占用,不需要显示调用此方法,线程结束后,对应的局部变量会自动回收。
initialValue():返回当前线程的局部变量的初始。
protected T initialValue() {
return null;
}
此方法是protected的,一般是子类实现,赋值初始值,无法被显式调用。
ThreadLocal使用场景
- 数据库链接
- session管理
总结
对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,比如定义一个static变量,同步访问,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。 ThreadLocal并不是为了解决多线程的数据共享,只是从另外一个方向来解决并发的问题,让变量线程局部化,就不存在并发。