基础知识:
ThreadLocal 内部是用一个map结构(threadLocalMap),key为线程对应的ThreadLocal对象,value为业务对象,可以将多个线程隔离开来。
先看类图:
重点看一下threadLocalMap的源码,它是一个简单的entry数组,键值key为线程对应的ThreadLocal对象,value为业务对象,在数组中的存储位置由key的threadLocalHashCode对INITIAL_CAPACITY取余数得到。
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;
/**
* 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
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* 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);
}
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
问题1: 内部map为何不用HashMap或者ConcurrentHashMap来实现?
ThreadLocal在实现上,是让每个线程在自己的内部单独持有一个变量,线程之间就不会有竞争出现,所以不需要采用并发对象ConcurrentHashMap。
不使用HashMap原因
ThreadLocalMap不存在多个key具有相同hashcode的问题,因而数据存储不需要像hashmap那样设计成数组+链表的形式,只需要数组就可以。
一个线程生成后,很久没有再次调用,我们为了保证系统的可用性,需要对线程资源进行回收,因而ThreadLocalMap的entry是需要继承 WeakReference的,便于即使回收。
问题2:threadLocalHashCode到底用来做什么的?
这个参数,我们是用来唯一确定一个ThreadLocal对象的
但是如何保证两个同时实例化的ThreadLocal对象有不同的threadLocalHashCode属性呢?在ThreadLocal类中,还包含了一个static修饰的AtomicInteger成员变量和一个static final修饰的常量(作为两个相邻nextHashCode的差值)。由于nextHashCode是类变量,所以每一次调用ThreadLocal类都可以保证nextHashCode被更新到新的值,并且下一次调用ThreadLocal类这个被更新的值仍然可用,同时AtomicInteger保证了nextHashCode自增的原子性。
确定了唯一的ThreadLocal对象,threadLocalHashCode还作为确定当前线程的ThreadLocalMap的table数组的位置(table数组其实就是Entry数组)
问题3:为什么不直接用线程id来作为ThreadLocalMap的key?
这一点很容易理解,因为直接用线程id来作为ThreadLocalMap的key,无法区分放入ThreadLocalMap中的多个value。比如我们放入了两个字符串,你如何知道我要取出来的是哪一个字符串呢?
而使用ThreadLocal作为key就不一样了,由于每一个ThreadLocal对象都可以由threadLocalHashCode属性唯一区分或者说每一个ThreadLocal对象都可以由这个对象的名字唯一区分,所以可以用不同的ThreadLocal作为key,区分不同的value,方便存取。
问题4:ThreadLocal使用场景与存在的风险?
在保持用户登录态的这个需求下,一般用 ThreadLocal 存储用户信息,而不是采用常见的 Cookie + Session。参考:threadLocal优势
存在的风险:
1:脏读(脏数据): 在一个线程中读取到了不属于自己的数据 。
线程使用 ThreadLocal 不会出现脏读,每个线程都是用的是自己的变量值和 ThreadLocal
线程池使用 ThreadLocal 会出现脏数据,线程池会复用线程,复用线程之后,也会复用线程中的静态属性,从而导致某些方法不能被执行,于是就出现了脏数据的问题
2.ThreadLocal 内存溢出问题(最常出现的问题)
内存泄漏:当一个线程执行完之后,不会释放这个线程所占用内存,或着释放内存不及时(线程不用了,但线程相关内存还得不到及时释放