概述
ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本变量进行映射,各个线程之间变量互不干扰。
如何使用
先看个简单的栗子学习如何使用ThreadLocal
public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, InterruptedException {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
Thread thread1 = new Thread() {
@Override
public void run() {
try {
threadLocal.set("Reyco");
Thread.sleep(1000);
System.out.println(threadLocal.get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
Thread thread2 = new Thread() {
@Override
public void run() {
try {
threadLocal.set("Abenl");
Thread.sleep(1000);
System.out.println(threadLocal.get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(threadLocal.get());
}
打印结果:
Reyco
Abenl
null
在thread1和thread2打印出来的值,都是各自原先存放在自己线程的,互不干扰。最好main线程打印出来的是null,因为在main线程中没有对ThreadLocal赋值,所以打印出默认值null。
自己实现的MyThreadLocal
public class MThreadLocal<T> {
//ThreadLocal的实现是依靠Map的映射
//Map将Thread作为key,存放的变量作为value
//形成了Thread和value的对应关系,就实现了本地线程storage
private final Map<Thread,T> storage=new HashMap<>();
public void set(T t) {
synchronized (this) {
//获取操作此方法的线程作为key
Thread thread=Thread.currentThread();
storage.put(thread, t);
}
}
public T get() {
T t;
synchronized (this) {
//获取操作此方法的线程作为key
Thread thread=Thread.currentThread();
t=storage.get(thread);
if(t==null) {
return initValue();
}else {
return t;
}
}
}
//默认ERROR
public T initValue() {
return (T) "ERROR";
}
}
深入剖析ThreadLocal
ThreadLocal核心机制:
- 每个Thread线程内部都有一个ThreadLocalMap
- 一个ThreadLocal只能在一个线程中存放一个变量,多个变量的存储需要创建多个ThreadLocal
- Map里面存储当前ThreadLocal(Key)和线程的变量副本(value)
- 当为ThreadLocal类的对象赋值时,首先获得当前线程的ThreadLocalMap属性,然后以该ThreadLocal类对象为key,设定value。Get方法亦然。
- 因为每次ThreadLocal类对象的操作,都是以获得当前线程为条件进行下去的,也就是不同线程去操作,会有不同的ThreadLocalMap,以此实现线程隔离。
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程对应的threadLocals
ThreadLocalMap map = getMap(t);
if (map != null) {
//Entry中key为当前threadLocals
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
//获取线程的ThreadLocalMap对象threadLocals
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//获取Entry
//Entry节点中key为当前ThreadLocals
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);
}
Get流程:
- 获取当前线程
- 获取当前线程对应的threadLocals
- 从Map中获取线程存储的K-V Entry节点
- 从Entry节点获取value
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程对应的threadlocals
ThreadLocalMap map = getMap(t);
if (map != null)
//设置Entry节点value
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap
ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也是独立实现。
Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap中的Entry继承自WeakReference,也就是弱引用(生命周期只能存活到下次生命周期前)。Entry中的key被限制为ThreadLocal,但只有ThreadLocal为弱引用类型,value不是。
Hash冲突
Hash冲突就是一个对象插入散列表,可是发现这个对象应该插入散列表的位置已经存放了其他的对象。
ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
显然ThreadLocalMap(在各自线程中)采用线性探测的方式解决Hash冲突的效率很低,如果有大量不同的ThreadLocal对象放入ThreadLocalMap中时发生冲突,或者发生二次冲突,则效率很低。在一个线程中,一个ThreadLocal只能存放一个变量。如果需要存放多个变量,就需要创建多个ThreadLocal,这样的话就有可能使得一个线程中的ThreadLocalMap存放者多个ThreadLocal,使得产生Hash冲突的可能性增大。
良好建议是:每个线程只存一个变量,这样的话所有的线程存放到ThreadLocalMap中的Key都是相同的ThreadLocal(Map的key是ThreadLocal)。因为如果一个线程要保存多个变量,就需要创建多个ThreadLocal,多个ThreadLocal放入Map中时会极大的增加Hash冲突的可能