1、ThreadLocal起到数据隔离的作用,相当于为每个线程提供一个变量副本,变量数据对别的线程而言是相对隔离的。在保存多线程环境下,防止自己的变量被其他线程纂改。
使用方式:线程中初始化一个ThreadLocal对象,后续只要改线程在remove之前调用get()方法取值即可。
ThreadLocal<String> localName = new ThreadLocal();
localName.set("Alice");
String name = localName.get();
localName.remove();
从以上的使用可以看出,ThreadLocal的使用主要涉及的接口:
void set(Object value)设置当前线程的局部变量值;
Object get() 返回当前线程所对应的局部变量值;
void remove() 删除当前线程局部变量的值,为了减少内存的占用。注意:当线程结束后,该线程的布局变量将自动被垃圾回收,所以显示调用该方法清楚并不是必须的,只是可以加快内存的回收速度。
2、常见的场景:
其中Spring中的事务就是采用ThreadLocal方式实现。Spring采用ThreadLocal的方式,来保存单个线程中的数据库操作使用的是同一个数据库连接。同时采用这种方式可以使业务层使用事务时不需要感知并管理Connection对象,通过传播级别巧妙的管理多个事务配置之间的切换、挂起和恢复。
在Android中,Looper类就是利用了ThreadLocal的特性,保证每个线程只存在一个Looper对象。
Looper的源码如下:
// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
3、接下来看下 ThreadLocal的源码解析
Thread类中有一个ThreadLocal.ThreadLocalMap 类型的属性变量 threadLocals
ThreadLocal.ThreadLocalMap threadLocals = null;
而ThreadLocal的静态内部类ThreadLocalMap类 中是由 Entry 类型数组table保存每一个数值,
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
其中, Entry 是包装成ThreadLocal<?>的弱引用static class Entry extends WeakReference<ThreadLocal<?>>
下面我们看下ThreadLocal类的 set(T value)方法源码
public void set(T value) {
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//获取ThreadLocalMap对象
if (map != null)
map.set(this, value);//设置值
else
createMap(t, value);//创建Map对象
}
以上set源码很简单明了,就是先获取当前现在的ThreadLocalMap对象,若存在直接设置,若不存在先创建Map对象,再设置。
然后我们先看下新建ThreadLocal类的 createMap(t, value);方法创建Map对象的源码,其中,this是当前的ThreadLocal对象
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
重点是ThreadLocalMap的构造方法源码:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY]; //新建Entry数组,初始大小是INITIAL_CAPACITY = 16
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);// 根据斐波拉契散列获取下标值
table[i] = new Entry(firstKey, firstValue); //构建Entry对象,放置数组中
size = 1;
setThreshold(INITIAL_CAPACITY); //设置扩容因子
}
其中
// 斐波拉契散列魔数
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out. Updated atomically. Starts at
* zero.
*/
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;// 魔数
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT); // 通过 AtomicInteger 类实现原子递增
}
再来看下 ThreadLocalMap 对象的set方法
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be 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); // 通过斐波拉契散列获取线程应该所在的索引下标
// 获取数组中该下标的值,判断是否已经存在元素
// nextIndex:获取下一个下标,通过三元表达式实现首尾相连
// 如果Entry为空,说明当前 TheadLocal 实例不存在,同时也不可能存在在其他索引位置,分析如下:
// 首先GC回收失效只对ThreadLocal进行清理,不会对Entry进行清理,所以Entry只会为空,不会为null
// 此时如果当前 ThreadLocal 在此Entry有效时插入,则会顺序寻找下一个槽位。
// 此时需要做的就是从其他槽位找到当前ThreadLocal,并把当前 ThreadLocal 拉回到正确槽位
// 其次,如果当前线程在插入 ThreadLocal 时所在槽位已经被占用,此时该 ThreadLocal 占用其他槽位,
// 之后如果占用该槽位的 ThreadLocal 被程序清理,注意程序清理会直接将Entry置null
// 清理完成后,程序会重新向下整理一次元素的下标位置,尽量将元素放在hash算出的hash位置
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果位置i不为空,这个Entry对象的key正好是即将设置的key,那么就刷新Entry中的value;
if (k == key) {
e.value = value;
return;
}
//如果当前位置是空的,就初始化一个Entry对象放在位置i上
if (k == null) {
replaceStaleEntry(key, value, i);// 清理并存储当前节点
return;
}
//如果位置i的不为空,而且key不等于entry,那就找下一个空位置,直到为空为止。
}
// 如果当前下标未被占用,则直接初始化为当前 ThreadLocal
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
再看下ThreadLocal的get源码
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();// map为空或者entry为空,构建并填充初值
}
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;
}
ThreadLocalMap的getEntry(ThreadLocal<?> key)源码:从ThreadLocalMap中获取Entry对象
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);//获取索引位置
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e); //key不为当前ThreadLocal,则说明已经被占用了,向后继续寻找
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// e不为空,说明槽点被占用,继续向后寻找
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);//失效直接移除
else
i = nextIndex(i, len); // 未失效,说明存在,继续下一个索引寻找
e = tab[i];
}
return null;
}
ThreadLocal的remove源码
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);//map存在移除当前的ThreadLocal
}
再看下ThreadLocalMap的remove
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 从下标位置开始,向下寻找,获取指定的 ThreadLocal后直接移除
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
类之间的关系
ThreadLocal的类图.png
网友评论