ThreadLocal能干什么?
ThreadLocal是当前线程的副本,一般用于数据隔离,填充的数据只属于当前线程,对别的线程不可见
ThreadLocal的原理?
- 如何使用?
public class ThreadLocalTest2 {
public static void main(String[] args) throws InterruptedException {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("wanghongwei");
threadLocal.get();
threadLocal.remove();
}
}
- set()源码:
public void set(T value) {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 获取线程的ThreadLocalMap对象
if (map != null) // 校验对象是否为空
map.set(this, value); //不为空set
else
createMap(t, value); // 为空创建一个map对象
}
- getMap(t)的实现
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看到ThreadLocalMap是当前线程Thread的一个变量threadLocals
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
到这就可以明白线程间数据隔离是怎么实现的了,每个线程维护了一个ThreadLocalMap对象,再set(value)时,实际上是在threadLocals变量中,别的线程无法获取到,从而实现了线程间的数据隔离
ThreadLocalMap的底层结构
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16; // table的初始化的大小,必须是2的n次方
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table; // 存储数据的主体
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // 阈值 Default to 0
变量table是存放数据的主体
table为什么是数组结构?
一个线程Thread可以有多个ThreadLocal,存放不同类型的数据,他们都需要存放在当前线程的ThreadLocalMap中,所以需要数组存放,类似这种
public class ThreadLocalTest2 {
public static void main(String[] args) throws InterruptedException {
ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
threadLocal1.set("hello world");
System.out.println("ThreadName: " + Thread.currentThread().getName() + ", 值:" +threadLocal1.get() );
threadLocal1.remove();
threadLocal2.set(10);
System.out.println("ThreadName: " + Thread.currentThread().getName() + ", 值:" +threadLocal2.get() );
threadLocal2.remove();
}
}
如何解决hash冲突问题
- 使用了开放寻址法:一旦发生冲突,就去寻找下一个空的散列地址
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); //定位到table中的位置
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) { // 刷新entry中的value
e.value = value;
return;
}
if (k == null) { // key为null
//被GC回收的话,就需要替换过期的值,把新的值放到这里
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash(); //扩容
}
threadLocal为什么使用弱引用
内存原理图
下图是程序运行中的内存分布图,简要介绍一下这种图:当前线程有一个threadLocals属性(ThreadLocalMap属性),该map的底层是数组,每个数组元素时Entry类型,Entry类型的key是ThreadLocal类型(也就是创建的ThreadLocal对象),而value是则是ThreadLocal.set()方法设置的value。
- 假设threadLocal是强引用,也就是连线5是强引用,即使栈内存中ThreadLocal的引用被清楚,但是因为连线5是强引用,所以堆中ThreadLocal对象也不会被清除。如果当前的线程不结束,那么堆中ThreadLocal对象会一直存在
- 假设Entry是弱引用,此时当栈内存中ThreadLocal的引用被清除后,因为连线5是弱引用,所以当发生gc时,堆内存中的ThreadLocal对象会被回收,再加上ThreadLocalMap的每次get、set、remove,都会清理key=null的Entry,所以使用弱引用能有效的防止内存泄漏
ThreadLocal发生内存泄漏的原因
-- 前提条件:没有进行手动的remove()
- ThreadLocal是静态变量,外部的强引用一直存在,使用过之后,没有进行remove()
- 使用了线程池,当线程执行完之后,会被放回线程池,value的外部强引用还在,无法被回收 解决办法:使用完之后,调用remove()方法
如何实现ThreadLocal数据被多个线程共享
使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
threadLocal.set("测试1");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threadlocal的值:" + threadLocal.get());
}
});
thread.start();
}
数据如何传递的?
public
class Thread implements Runnable{
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
......
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
......
}
}
如果线程的inheritableThreadLocals为true,并且父线程的inheritableThreadLocals存在,那么就把父线程的inheritableThreadLocals传递给子线程
#参考文档