ThreadLoacal:线程内局部变量,对于一个相同的代码模块,每个线程访问的时候代码模块内的变量互不干扰,互不影响。相当于为每个线程创建一个单独变量的副本,相当于private static类型变量。在多线程中保证各个线程里的变量独立于其他线程的变量。
目的是解决变量在单个线程内部变量的传递问题,ThreadLocal修饰的变量多个线程之间不共享,不存在安全性问题,所以它并不是解决多线程之间的安全问题
ThreadLocal的作用和同步机制有些相反:同步机制是为了保证多线程下数据的一致性,ThreadLocal保证了多线程条件下数据的独立性。
ThreadLocal提供了线程本地变量,可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同,而同一个线程在任何情况下访问这个变量的结果都是一致的,当这个线程结束生命周期时,所有的线程的本地实例都会被GC,ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定;作用域是线程内部,伴随线程执行始终,线程结束,变量生命结束。
源码分析:
核心是通过静态内部类ThreadLocalMap来处理数据
static class ThreadLocalMap {
//entry是ThreadLocalMap核心存储kv键值对的,继承WeakReference,entry所对应的key(也就是ThreadLocal的实例)的引用是弱引用。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
get():返回此线程局部变量中当前线程副本中的值
获取当前线程的ThreadLocalMap,如果map不为空,就将获得key值作为当前ThreadLocal的值,否则就调用setInitialValue方法返回初始值,并保存到新的ThreadLocalMap中。
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作用是给编译器一条指令,告诉他被批注的代码元素内部的某些警告保持静默。
//在这里是一直没有权限访问的域的警告。
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果map为空就创建一个新Map并返回它(也就是空值)
return setInitialValue();
}
set(T value):设置当前线程的局部变量值
ThreadLocalMap相当于一个HashMap,是真正保存值的地方。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//如果map不为空就将当前的key和value更新到map中,用当前的ThreadLocal作为key,
//否则就创建一个Map并给到当前线程,并把当前线程作为key值,和value保存到map中。
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
remove():将当前线程变量的值删除
目的是减少内存的占用,不需要显示调用此方法,线程结束后,对应的局部变量会自动回收。
每个线程都会持有一个ThreadLocalMap,用来维护线程本地的值。
在使用ThreadLocal变量进行操作时,都会通过当前线程获取到ThreadLocalMap来完成操作,每个线程的ThreadLocalMap都是自己的,ThreadLocalMap维护的值也是自己的,这就保证了线程变量的独立性。
ThreadLocalMap
构造方法(有两个构造方法):当前ThreadLocalMap为null时会使用它来构建一个ThreadLocalMap
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);
}
存储结构:内部维护了一个哈希表(数组)来存储数据
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
//阈值默认为0
private int threshold;
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
table是一个entry类型的数组,Entry是ThreadLocalMap的一个内部类
保存键值对:调用set(ThreadLocal key,Object value)方法将数据保存到哈希表中
首先使用key值也就是当前ThreadLocal的threadLocalHashCode来计算要存储的索引位置i,每创建一个ThreadLocal对象都会自动生成一个threadLocalHashCode值。
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//索引要存储的位置
int i = key.threadLocalHashCode & (len-1);
//循环判断要存放的索引位置是否已经存在Entry,若存在,进入循环体
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果索引位置的Entry和要保存的key值相等,更新Entry的值
if (k == key) {
e.value = value;
return;
}
//如果索引位置的Entry为null,就说明这个Entry已经无效,用要保存的键值对替换他
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//循环结束了也没有找到就将要保存的键值对作为一个新的Entry保存在该位置
tab[i] = new Entry(key, value);
//将数组的长度增加
int sz = ++size;
//清除一些无效的条目并且判断table中的元素是否已经超过了阈值
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
在保存数据的时候,如果索引位置有Entry并且Entry的key值为null,那么就会执行清除无效的Entry的操作,因为Entry的key使用的是弱引用方式,key如果被回收,就无法访问到key对应的value,所以这种Entry无效,将他们清除掉来腾出空间。
在整理table容量的时候也要清除无效对象,然后再根据需要扩容
private void rehash() {
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}
获取Entry对象
private Entry getEntry(ThreadLocal<?> key) {
//使用指定key的HashCode计算索引位置
int i = key.threadLocalHashCode & (table.length - 1);
//获取当前位置的 Entry
Entry e = table[i];
//如果Entry不为null且Entry的key和指定的key相等,则返回该Entry
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
可能存在哈希冲突,key对应的Entry的存储位置可能不在通过key值计算出来的索引位置上,就要通过getEntryAfterMiss方法来获取。解决哈希冲突的方法是rehash
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
//索引位置上的Entry不为空进入循环,为null则返回null
while (e != null) {
ThreadLocal<?> k = e.get();
//如果Entry的key值和指定的key值相等,则返回该Entry对象
if (k == key)
return e;
//如果Entry的key为null,就清除无效Entry
//否则获取下一个Entry,循环进行判断
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
移除指定的Entry
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//循环判断索引位置的Entry是否为null
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//如果Entry的key和指定的key相等,执行删除操作
if (e.get() == key) {
//清除key的引用
e.clear();
//清除无效Entry
expungeStaleEntry(i);
return;
}
}
}
内存泄漏
ThreadLocalMap的set()、get()和remove()方法中都有清除无效Entry的操作,这样做是为了降低内存泄漏的可能性。
Entry中的key使用了弱引用的方式,这样做就是为了减少内存泄漏,但并不能完全避免内存泄漏。
如果Entry中的key使用的是强引用,由于ThreadLocalMap的生命周期和当前线程一样长,那么当引用ThreadLocal的对象回收以后,由于ThreadLocalMap还持有ThreadLocal和对应的value的强引用,ThreadLocal和对应的value是不会被回收的,这就导致了内存泄漏,但是此时的value还是无法被回收的,还是会导致内存泄漏。
ThreadLocalMap就考虑到了这个情况,并且在这些方法中进修表格清除无效Entry操作,所以我们在使用ThreadLocal都调用remove()方法清除数据,就能防止内存泄漏。
ThreadLocal使用场景
数据库链接和session管理