1、ThreadLocal
利用threadlocal主要是为了达到线程隔离,实现线程安全
threadlocal用了hashmap的思想,key是固定的:就是当前线程
一、 ThreadLocal
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这 个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个 线程上的一个值。
可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。
如比我们初始化一个ThreadLocal变量 = 0
//可以理解为 一个map,类型 Map<Thread,Integer>
//实际上thread底层就是这么实现的
static ThreadLocal<Integer> threadLaocl = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
启3个线程,A B C ;线程A 对ThreadLocal 加 1 一定得到1
线程B 对ThreadLocal 加 2 一定得到2
线程B 对ThreadLocal 加 3 一定得到3
每个线程对 ThreadLocal 变量的操作,其它线程影响不了。
ThreadLocal底层实际是由一个map实现线的,Key为当前线程,value就是初始化的值。
所以每个线程取ThreadLocal 值时就是通过value = map.get(thread)
获取到每个线程自己的变量 进行操作。
二、 ThreadLocal常见用法
ThreadLocal常用于线程池,每个线程都需要有一个自己的链接,用ThreadLocal保存,那么每个线程互不影响
三、threadLocal get set 方法
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("test");
threadLocal.get();
//set
public void set(T value) {
Thread t = Thread.currentThread();
//获取当前线程的成员变量ThreadLocalMap,不同的线程获取到不同对象
ThreadLocalMap map = getMap(t);
if (map != null)
//当前threadLocal对象,和value
map.set(this, value);
else
createMap(t, value);
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//根据threadLocal对象hashCode,定位entry数据中的位置
//同一个线程中定义的不同threadLocal对象会定位到不同的位置
//key.threadLocalHashCode得到这个对象的hashcode值,
//这个值是一个final int类型在new ThreadLocal对象的时候初始化好的
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
//有值覆盖
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//无值在定位到的位置上新增一个对象
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
//key.threadLocalHashCode:定义
//通过自旋cas的方式获取hashcode,
//每个new出来的threadLocal对象hashcode值不一样
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
//get
public T get() {
Thread t = Thread.currentThread();
//获取当前线程的成员变量ThreadLocalMap,不同的线程获取到不同对象
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();
}
1.1、ThreadLoalMap
ThreadLoalMap它也是一个类似HashMap的数据结构,但是在ThreadLocal中,并没实现Map接口。
在ThreadLoalMap中,是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象,通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中。
1.1.1、 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);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
- 1、如果当前位置是空的,就初始化一个Entry对象放在位置i上;
- 2、位置i已经有Entry对象了,如果这个Entry对象的key正好是即将设置的key,那么重新设置Entry中的value;
- 3、位置i的Entry对象,和即将设置的key没关系,那么只能找下一个空位置;
1.1.2、内存泄漏
原因:ThreadLocalMap的实现中,key被保存到了WeakReference对象中。这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
java有4种引用类型
强引用 GC也不会被回收
软引用 内存不够才会被回收
弱引用 只能存活到下次GC
虚引用 随时可能被回收
1.1.3、如何避免内存泄露
在调用ThreadLocal的get()、set()清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有GC Roots可达了,下次GC的时候就可以被回收,当然如果调用remove方法,肯定会删除对应的Entry对象。
try {
localName.set("test");
// 其它业务逻辑
} finally {
localName.remove();
}