ThreadLocal
线程局部变量,应用场景多线程。将公共变量复制一份到当前线程中,避免了线程之间使用同一个变量而产生线程安全问题。线程之间的变量是相互隔离的,互不影响,使线程安全运行。
区别
ThreadLocal与synchronized 之间的区别,同样起到使线程安全的作用,两者之间的区别在实现线程安全的方式不一样。
ThreadLocal:将存在线程安全隐患的变量复制一份到当前线程中。线程并发访问,一个线程一份变量。以空间换时间。
synchronized:将存在安全隐患的过程让线程排队执行。线程排队访问,公用一个变量。以时间换空间。
ThreadLocal原理
jdk8以前,由ThreadLocal维护ThreadLocalMap,ThreadLocalMap中Entry的key为Thread,value为此线程绑定的变量。
jdk8以后,由线程Thread维护ThreadLocalMap,ThreadLocalMap中Entry的key为ThreadLocal,value为绑定线程的变量。
以jdk8为例,ThreadLocalMap是作为Thread的成员变量存在
在Thread中定义:
在ThreadLocal中,主要方法set,get,remove都会调用getMap(Thread t)方法获取当前线程的的ThreadLocalMap,对ThreadLocalMap中的Entry进行操作(ThreadLocalMap不是Map)
// 获取当前线程的 ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//
public void set(T value) {
Thread t = Thread.currentThread();
// 获取当前线程的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
// 获取当前线程的 ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 当ThreadLocalMap为空时,创建ThreadLocalMap并绑定值
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//
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);
}
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();
}
// 移除Entry
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
内存泄漏
当程序中已经动态分配的堆内存,未释放或无法释放时会造成内存的浪费,导致程序运行变慢甚至崩溃。内存泄漏的堆积会造成最终导致内存溢出。
虽然Thread生命周期结束后ThreadLocal也会结束,但现在基本使用线程池的情况下,线程使用完成并不会就此结束,而是回到线程池中等待下次被调用,所以当ThreadLocal使用完后最好调用remove()释放Entry,防止出现内存泄漏。
Spring 自动注入单例或多例的选择
singleton:通过容器获得的总是同一个实例,开销小,当含有有状态的成员变量时,可使用ThreadLocal保证线程的安全性
prototype:每次获取的都是一个新的实例,对CPU、内存的消耗高,线程安全
高并发时使用singleton性能比较好