探索ThreadLocal

2021/04/24 923点热度 0人点赞 0条评论

图片

先看下Thread的代码结构

public class Thread implements Runnable {
//默认线程里存放的为空,维护的是当前线程关联的的ThreadLocal.ThreadLocalMap的值
ThreadLocal.ThreadLocalMap threadLocals = null;
//线程退出时
private void exit() {
//解除了线程和threadloca的关系
threadLocals = null;
}
}

再看下ThreadLocal的代码结构

public class ThreadLocal<T> {
//获取唯一的hashcode
private final int threadLocalHashCode = nextHashCode();

//0开始自增的一个数
private static AtomicInteger nextHashCode =new AtomicInteger();
//斐波那契散列 1640531527
private static final int HASH_INCREMENT = 0x61c88647;

//返回一个next hash
private static int nextHashCode() {
//每初始化一次,就原子自增HASH_INCREMENT
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
//实例化没有做任何操作
public ThreadLocal() {
}
//get操作
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();
}
//set操作
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 getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
//新创建map的时候,是给当前线程挂上ThreadLocalMap的引用
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//静态内部类,全局共享
static class ThreadLocalMap {
//弱引用,并且全局唯一
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//首次set的的时候
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
/**为了让哈希码能均匀的分布在2N次方的数组里.ThreadLocalMap Entry[] table *的大小必须是2N次方(len = 2^N),那 len-1 的二进制表示就是低位连续的N1也就是 *threadLocalHashCode 的低N 这样就能均匀的产生均匀的分布
*/

int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
//再次set的时候
private void set(ThreadLocal<?> key, Object value) {
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) {
//针对所有已经被gc掉的threadLocak进行处理
replaceStaleEntry(key, value, i);
return;
}
}
//没有找到,就创建一个新的Entry,新增之前看是否需要扩容
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
//资源释放
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
//调用WeakReferenceclear方法清除对ThreadLocal的弱引用
e.clear();
//清除keynull的元素
expungeStaleEntry(i);
return;
}
}
}
}

}

魔数测试

  private  AtomicInteger nextHashCode = new AtomicInteger();
private final int HASH_INCREMENT = 0x61c88647;
public void magicHash(int len){
int hashCode = 0;
System.out.println(Integer.toBinaryString(len-1));
for(int i=0 ; i<len ; i++){
//相当于n倍的HASH_INCREMENT
hashCode = nextHashCode.addAndGet(HASH_INCREMENT);
//System.out.println(Integer.toBinaryString(hashCode));
//因为&运算只有同为1才为1,那么最后只会保留后n位做&运算,
System.out.println((hashCode & (len-1))+" ");
}
}

@Test
public void testMagicHash(){
System.out.println("16长度的数组");
magicHash(16);
System.out.println("");
System.out.println("32长度的数组");
magicHash(32);
}

位运算规则: 与运算(&) 与运算规则:0&0=0;0&1=0;1&0=0;1&1=1 即:两个同时为1,结果为1,否则为0

例如:3&5

十进制3转为二进制的3:0000 0011

十进制5转为二进制的5:0000 0101

------------------------结果:0000 0001 ->转为十进制:1

即:3&5 = 1

或运算(|)

运算规则:0|0=0;0|1=1;1|0=1;1|1=1;

即 :参加运算的两个对象,一个为1,其值为1。

例如:3|5 即 00000011| 0000 0101 = 00000111,因此,3|5=7。

异或运算符(^)

运算规则:00=0;01=1;1^0=1;1^1=0;

即:参加运算的两个对象,如果两个位为“异”(值不同),则该位结果为1,否则为0。

例如:3^5 = 0000 0011| 0000 0101 =0000 0110,因此,3^5 =6

总结:

ThreadLocal它是依附在Thread上的,所以他们的生命周期一样,如果使用的线程属于属于共享资源比如线程池,再没有新任务进来的时候,该线程的threadLocal不会被清理掉,如果这个时候发生了GC,Entry是弱引用,会被优先释放,value值因为强引用,不会释放,会导致内存溢出。

这种情况下在:tomcat 使用线程池提供服务,后端跑批使用线程池的时候都容易出现。

所以要使用ThreadLocal要养成良好的习惯,用完就remove调。手动进行释放。

yxkong

这个人很懒,什么都没留下

文章评论