前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ThreadLocal原理及源码解析(一步一步点进去,不要背了,学思想)

ThreadLocal原理及源码解析(一步一步点进去,不要背了,学思想)

作者头像
向着百万年薪努力的小赵
发布2022-12-02 10:38:09
4010
发布2022-12-02 10:38:09
举报
文章被收录于专栏:小赵的Java学习

文章内容引用自 咕泡科技 咕泡出品,必属精品

文章目录

1ThreadLocal使用

我们知道,一个共享变量或者资源,在多个线程操作的时候,肯定是会相互影响,不能隔离的

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
public class AtomicTest {

	int i=0;
	public void incr(){
		i+=10;
		System.out.println(i);
	}
	
	public static void main(String[] args) throws InterruptedException {
		AtomicTest test=new AtomicTest();
		Thread[] threads=new Thread[5];
		for (int j = 0; j < 5; j++) {
			threads[j] =new Thread(() -> {
				test.incr();
			});
			threads[j].start();
		}
		for (int j = 0; j < 5; j++) {
		threads[j].join();
		}
	}
}

比如,这样的代码,那么得到的结果,肯定是每次加10不定,还有因为原子性问题,肯定会出现相同的10.20…。

那么假如我有场景,我要做到线程之前的数据相互不影响!!相互隔离,也就是我们讲的线程安全。比如mybatis里面,sqlsession就是存在ThreadLocal里面的,sqlSession这个对象就是线程安全的!!那么实现方式就是我们今天讲的重点:threadLocal

上面的栗子怎么 变得香甜 实现线程安全?如下:

代码语言:javascript
复制
public class ThreadLocalTest {
	ThreadLocal<Integer> integerThreadLocal=new ThreadLocal<Integer>() {
		public Integer initialValue() {
			return 10;
		}
	};
	ThreadLocal<Integer> integerThreadLocal1=new ThreadLocal<Integer>() {
		public Integer initialValue() {
			return 20;
		}
	};
	public void incr() {
		int value = integerThreadLocal.get().intValue();
		integerThreadLocal.set(value += 10);
		int value1= integerThreadLocal1.get().intValue();
		integerThreadLocal1.set(value1 += 10);
		System.out.println(integerThreadLocal.get());
		System.out.println(integerThreadLocal1.get());
	}
	public static void main(String[] args) {
		ThreadLocalTest test=new ThreadLocalTest();
		Thread[] threads=new Thread[5];
		for (int j = 0; j < 2; j++) {
			threads[j] =new Thread(() -> {
			test.incr();
		});
	}

这样我们integerThreadLocal得到的结果都是20,integerThreadLocal1得到的结果都是30。

2ThreadLocal原理源码分析

我们从2个角度去分析源码,一个是get 一个是set

2.1get方法

2.1.1入口

代码语言:javascript
复制
    public T get() {
    	//获取当前线程
        Thread t = Thread.currentThread();
        //去根据Thread拿ThreadLocalMap,我们发现Thread类下会有个数据对象叫做ThreadLocalMap
        //1.1 线程第一次进来,map肯定是null
        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;
            }
        }
        //1.2进入初始化方法
        return setInitialValue();
    }
2.1.1.1ThreadLocal中的ThreadLocalMap对象

ThreadLocalMap对象,里面有个Entry的 key 、value的结构

代码语言:javascript
复制
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;
		}
	}
//..........其他信息省略..................
}

我们发现,Entry的key是一个对于ThreadLocal这个对象的弱引用。

讲下四大引用 除了基础的数据类型外都是引用类型,那么java根据其生命周期的长短又将引用类型分为强引用、软引用、弱引用、虚引用。

2.1.1.2强引用

也是我们平时用得最多的,new一个对象就是强引用,例如 Object obj = new Object(); 当JVM的内存空间不足时,宁愿抛出OutOfMemoryError使得程序异常终止也不愿意回收具有强引用的存活着的对象!记住是存活着,不可能是你new一个对象就永远不会被GC回收。 如果将引用赋值为null时,你的对象就表明不是存活着,这样就会可以被GC回收了

当内存不足的时候,jvm开始垃圾回收,对于强引用的对象,就算出现OOM也不会回收该对象的。 因此,强引用是造成java内存泄露的主要原因之一。

代码语言:javascript
复制
public static void main(String[] args) {
	Object obj=new Object();//这样定义就是一个强引用
	Object obj2=obj;//也是一个强引用
	obj=null;
	System.gc();
	//会不会被垃圾回收?
	System.out.println(obj2);
}
2.1.1.3软引用

软引用的生命周期比强引用短一些。软引用是通过SoftReference类实现的。当JVM认为内存空间不足时,就会去试图回收软引用指向的对象对于只有软引用的对象来说, 当系统内存充足时,不会被回收; 当系统内存不足时,会被回收;

代码语言:javascript
复制
Object obj=new Object();
SoftReference wrf=new SoftReference(obj);
obj=null;
System.out.println("未发生GC之前"+wrf.get());
System.gc();
System.out.println("内存充足,发生GC之后"+wrf.get());
2.1.1.4弱引用

弱引用是通过WeakReference类实现的,它的生命周期比软引用还要短,也是通过get()方法获取对象。在GC的时候,不管内存空间足不足都会回收这个对象,同样也可以配合ReferenceQueue使用,也同样适用于内存敏感的缓存。ThreadLocal中的key就用到了弱引用。

代码语言:javascript
复制
Object obj=new Object();
WeakReference wrf=new WeakReference(obj);
obj=null;
System.out.println("未发生GC之前"+wrf.get());
System.gc();
System.out.println("内存充足,发生GC之后"+wrf.get());
2.1.1.5虚引用

也称虚引用,是通过PhantomReference类实现的。任何时候可能被GC回收,就像没有引用一样。无法通过虚引用访问对象的任何属性或者函数。 那就要问了要它有什么用? 虚引用仅仅只是提供了一种确保对象被finalize以后来做某些事情的机制。比如说这个对象被回收之后发一个系统通知啊啥的。

虚引用是必须配合ReferenceQueue 使用的,具体使用方法和上面提到软引用的一样。主要用来跟踪对象被垃圾回收的活动。

我们知道了Entry的key是弱引用,弱引用的作用是什么我们知道了,那么至于为什么要用弱引用,我们等下再回来看,先把整个流程搞清楚!

2.1.2 初始化方法

代码语言:javascript
复制
private T setInitialValue() {
	//调用initialValue方法,默认为null,可以重写。重写设置初始值
	T value = initialValue();
	//获取当前线程
	Thread t = Thread.currentThread();
	//根据线程获取线程的ThreadLocalMap
	ThreadLocalMap map = getMap(t);
	//第一次初始化,为null
	if (map != null)
		map.set(this, value);
	else
		//走到创建Map逻辑 1.2.1
		createMap(t, value);
	return value;
}
初始化创建Map
代码语言:javascript
复制
void createMap(Thread t, T firstValue) {
	t.threadLocals = new ThreadLocalMap(this, firstValue);
}

进入ThreadLocalMap初始化构造函数

代码语言:javascript
复制
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue){
	//初始化Entry数组
	table = new Entry[INITIAL_CAPACITY];
	//根据线程的hashcode取模得到我应该放入数据的哪个下标位置
	int i = firstKey.threadLocalHashCode &(INITIAL_CAPACITY - 1);
	//在计算出的下标位置,放入entry,key为ThreadLocal对象,value为初始化的值
	table[i] = new Entry(firstKey, firstValue);
	size = 1;
	//设置ThreadLocalMap的threshold值,16*2/3=10
	setThreshold(INITIAL_CAPACITY);
}

自此,初始化流程完成!!

代码注释自认为给的很详细了

在这里插入图片描述
在这里插入图片描述

2.2Set方法

2.2.1.set入口

代码语言:javascript
复制
public void set(T value) {
	//获取当前线程
	Thread t = Thread.currentThread();
	//获取线程的Map,这个时候,我们上面get的时候已经初始化,已经有值
	ThreadLocalMap map = getMap(t);
	//get如果在map之前执行,肯定不为null
	if (map != null)
	//进入set方法 
		map.set(this, value);
	else
		createMap(t, value);
}
set方法
代码语言:javascript
复制
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.
	//将原有的table赋值给tab
	Entry[] tab = table;
	//得到tabl的大小
	int len = tab.length;
	//根据key的hashCode 取模数组,得到数据的下标,同一个key的时候,hashcode一样,所以
	//根据key找到的下标已经有entry对象并且已经赋值了初始化的值
	int i = key.threadLocalHashCode & (len-1);
	//在同一个key的get.set之后,e不为null,进入for循环
	for (Entry e = tab[i];
		e != null;
		e = tab[i = nextIndex(i, len)]) {
			//得到entry的key
			ThreadLocal<?> k = e.get();
			//因为get set传入的threadlocal对象是一个,满足条件
			if (k == key) {
				//将entry对象的value更改为新的value,返回
				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();
	}

这个是最基础的流程,我们大概可以整理流程图如下:

假如2个线程,操作integerThreadLocal这个ThreadLocal对象,并且ThreadLocal对象的hash值计算后在Entry中的数组下标为5,integerThreadLocal下标为3 栗子中的代码呦

正常流程如下: get方法后

在这里插入图片描述
在这里插入图片描述

Set方法后

在这里插入图片描述
在这里插入图片描述

多个线程,就是多个外面的Thread,做到线程之间数据隔离

看完了get、set,看一下remove吧

2.3remove方法

代码语言:javascript
复制
public void remove() {
	//根据当前线程获取ThreadLocalMap
	ThreadLocalMap m = getMap(Thread.currentThread());
	//如果ThreadLocalMap!=null
	if (m != null)
		//移除
		m.remove(this);
}
代码语言:javascript
复制
private void remove(ThreadLocal<?> key) {
	Entry[] tab = table;
	int len = tab.length;
	//根据key得到在数组中的下标位置
	int i = key.threadLocalHashCode & (len-1);
	for (Entry e = tab[i];
		e != null;
		e = tab[i = nextIndex(i, len)]) {
		//如果下标位置Entry的key为当前对象,进行整理清楚
		if (e.get() == key) {
			//解除Entry对key的弱引用
			e.clear();
			//对下标进行清除,并且对table进行整理
			expungeStaleEntry(i);
			return;
		}
	}
}

顺便看下扩容逻辑吧

2.4 扩容逻辑

入口还是在set方法

代码语言:javascript
复制
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;
	//没有进行清理并且size大于等于我的扩容界限,调用rehash
	if (!cleanSomeSlots(i, sz) && sz >= threshold)
		rehash();
}

Rehash方法

代码语言:javascript
复制
private void rehash() {
	expungeStaleEntries();
	// Use lower threshold for doubling to avoid hysteresis
	//当容量大于等于四分之三时,进入resize方法
	if (size >= threshold - threshold / 4)
		resize();
}

resize方法

代码语言:javascript
复制
private void resize() {
	Entry[] oldTab = table;
	int oldLen = oldTab.length;
	//扩容容量为原容量的2倍
	int newLen = oldLen * 2;
	//初始化数组长度
	Entry[] newTab = new Entry[newLen];
	int count = 0;
	//循环遍历老的容量大小
	for (int j = 0; j < oldLen; ++j) {
		//遍历Enrty
		Entry e = oldTab[j];
		//如果Entry不为null
		if (e != null) {
			//获取Entry的key
			ThreadLocal<?> k = e.get();
			//如果key为null,无效数据,把value设置为空,让value能gc回收
			if (k == null) {
				e.value = null; // Help the GC
			} else {
				//不为空,得到k的新的下标地址
				int h = k.threadLocalHashCode & (newLen - 1);
				//如果!=null.代表发生hash冲突
				while (newTab[h] != null)
					//线性探测下一个
					h = nextIndex(h, newLen);
				//赋值给为空的entry位置
				newTab[h] = e;
				count++;
			}
		}
	}
	//设置下一次的扩容值
	setThreshold(newLen);
	size = count;
	table = newTab;
}

2.5其他非正常情况

2.5.1当ThreadLocal与ThreadLocal1的hash值冲突

我们来看set方法中多线程中多个ThreadLocal的hashCode冲突时,怎么解决,我们回到set方法

代码语言:javascript
复制
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;
	//这里可能发送hash冲突,假如threadLocal1跟threadLocal 2个对象的hash值相同,下标都是5
	int i = key.threadLocalHashCode & (len-1);
	//通过i去拿数据的Entry,我们拿到的是ThreadLocal的,因为
	ThreadLocal占据了5这个位置
	for (Entry e = tab[i];
		e != null;
		e = tab[i = nextIndex(i, len)]) {
		//得到的是ThreadLocal对象
		ThreadLocal<?> k = e.get();
		//ThreadLocal !=ThreadLocal1
		if (k == key) {
			e.value = value;
			return;
		}
		//第一个循环 k也不等于null
		//第二轮循环,
		if (k == null) {
			replaceStaleEntry(key, value, i);
			return;
		}
		//3个条件都不满足,进入下一个循环
		//i= nextIndex(i, len) 去找下一个小标的位置,直到找到下一个key为空的为止,这个场景我们等下过
		//或者遍历完到一个null的位置,就不在循序
	}
	//找到一个为null的位置(肯定有,因为有扩容机制)
	tab[i] = new Entry(key, value);
	int sz = ++size;
	//清理后,如果超过我的扩容界限 扩容界限为三分之二,进行扩容
	if (!cleanSomeSlots(i, sz) && sz >= threshold)
		rehash();
}

冲突取值 我们知道它是用的线性探测去解决hash的,那么会出现一个问题?我根据hash去拿到的对象,可能不再是我自己想要的对象!

代码语言:javascript
复制
public T get() {
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null) {
	//根据threadLocal对象 去map中获取Entry,如果冲突了我们看下怎么拿
		ThreadLocalMap.Entry e = map.getEntry(this);
		if (e != null) {
			T result = (T)e.value;
			return result;
		}
	}
	return setInitialValue();
}

getEntry方法:

代码语言:javascript
复制
private Entry getEntry(ThreadLocal<?> key) {
	//根据key的hash下标值去取值
	int i = key.threadLocalHashCode & (table.length - 1);
	Entry e = table[i];
	//如果取到的enrty不为null 并且对象也是我需要的对象,直接返回
	if (e != null && e.get() == key)
		return e;
	else
		//如果不是我想要的对象,进入getEntryAfterMiss
		return getEntryAfterMiss(key, i, e);
}

getEntryAfterMiss方法:

代码语言:javascript
复制
//key:我需要get的对象 i 根据key计算出来的下标 e 下标中的当前值
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i,Entry e) {
	Entry[] tab = table;
	int len = tab.length;
	//如果e!=null。进入逻辑,如果e为null,说明ThreadLocal没有设置value,直接返回空
	while (e != null) {
		//得到当前位置的Entry对象
		ThreadLocal<?> k = e.get();
		//如果当前位置的Entry跟传进来的一致,直接返回
		if (k == key)
			return e;
		if (k == null)
			//如果对象的key被GC回收,进入整理逻辑,把当前位置设置为null 并且进行整理,rehash
			expungeStaleEntry(i);
		else
			//去下一个线性找
			i = nextIndex(i, len);
			//把e设置为下一个Enrty对象
		e = tab[i];
	}
	return null;
}

2.5.2当Key被GC回收处理

我们刚才讲过我们的key是弱引用,何为弱引用,就是我这个key就算外面有引用,只要发生GC也会被回收,就会出现我Entry的数据有可能是key为null ,但是value不为null的场景。 我们继续来看ThreadLocal怎么解决,继续回到set方法

代码语言:javascript
复制
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;
			}
		//因为Key被回收,所以key为null,会进入replaceStaleEntry方法
		if (k == null) {
			replaceStaleEntry(key, value, i);//这里这里
			return;
		}
	}
	//找到key为null的,不会走下面逻辑
	tab[i] = new Entry(key, value);
	int sz = ++size;
	if (!cleanSomeSlots(i, sz) && sz >= threshold)
		rehash();
}

replaceStaleEntry方法: 看这个方法,我们举个例子:在Thread1线程执行threadLocal1.set10);同时threadLocal1通过hash算法得到的下标为5;然后5的下标的key被GC回收,key=null。

代码语言:javascript
复制
//key为我需要获取值的ThreadLocal对象,value为需要set的值 i为key被回收的数组下标
//根据举例的场景:key为ThreadLocal1对象 value=10 i=5
private void replaceStaleEntry(ThreadLocal<?> key, Object value,int staleSlot) {
	Entry[] tab = table;
	int len = tab.length;
	Entry e;
	// Back up to check for prior stale entry in current run.
	// We clean out whole runs at a time to avoid continual
	// incremental rehashing due to garbage collector freeing
	// up refs in bunches (i.e., whenever the collector runs).
	//slotToExpunge为5
	int slotToExpunge = staleSlot;
	//向数组前面轮询找 找到一个null的entry为止 假如下标为4的entry为null,跳出循环
	for (int i = prevIndex(staleSlot, len);
	(e = tab[i]) != null;
	i = prevIndex(i, len))
	//假如下标为4的不是null,并且是被GC回收的,那么slotToExpunge赋值为向前找,找到最靠近null的被GC回收的Entry
	if (e.get() == null)slotToExpunge = i;
	// Find either the key or trailing null slot of run,whichever
	// occurs first
	//向后循环,找到entry为null为止
	for (int i = nextIndex(staleSlot, len);
		(e = tab[i]) != null;
		i = nextIndex(i, len)) {
		ThreadLocal<?> k = e.get();
		// If we find key, then we need to swap it
		// with the stale entry to maintain hash table order.
		// The newly stale slot, or any other stale slot
		// encountered above it, can then be sent to expungeStaleEntry
		// to remove or rehash all of the other entries in run.
		//假如向后找到了key跟我传入的一样的entry
		if (k == key) {
			//如果一样,替换value
			e.value = value;
			//假如下标为7的跟我传入的key是一样的
			tab[i] = tab[staleSlot];
			//在下标为5的位置放入7下的entry
			tab[staleSlot] = e;
			// Start expunge at preceding stale entry if it exists
			//如果slotToExpunge=slotToExpunge,则向前遍历没有找到key被回收的Entry
		if (slotToExpunge == staleSlot)
			//将slotToExpunge改成7
			slotToExpunge = i;
			//执行cleanSomeSlots方法
			cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);//expungeStaleEntry方法
			//返回
			return;
		}
		// If we didn't find stale entry on backward scan, the
		// first stale entry seen while scanning for key is the
		// first still present in the run.
		//如果循环到后面的也是被GC回收的,并且向前遍历没有找到key被回收的Entry
		if (k == null && slotToExpunge == staleSlot)
		//slotToExpunge设置为 key被GC回收的Entry的下标位置
			slotToExpunge = i;
	}
	// If key not found, put new entry in stale slot
	//回收的entry的value设置为null (利于value对象回收)
	tab[staleSlot].value = null;
	//在回收的下标位置,新建对象赋值
	tab[staleSlot] = new Entry(key, value);

	// If there are any other stale entries in run, expunge them
	//slotToExpunge!=staleSlot,需要向前或者向后有找到需要清理的Entry,执行cleanSomeSlots
	if (slotToExpunge != staleSlot)
		cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);//expungeStaleEntry方法
}

expungeStaleEntry方法:

代码语言:javascript
复制
private int expungeStaleEntry(int staleSlot) {
	Entry[] tab = table;
	int len = tab.length;
	// expunge entry at staleSlot
	//将传进来的下标位置的Entry value设置为null value就可以被GC回收了
	//将传进来的下标位置的Entry设置为null 清理空间
	tab[staleSlot].value = null;
	tab[staleSlot] = null;
	size--; //数组里的size-1
	// Rehash until we encounter null
	Entry e;
	int i;
	//根据传进来的位置向后遍历,遍历到null为止
	for (i = nextIndex(staleSlot, len);
		(e = tab[i]) != null;
		i = nextIndex(i, len)) {
		ThreadLocal<?> k = e.get();
		//如果entry对象的key被GC回收,清空entry
		if (k == null) {
			e.value = null;
			tab[i] = null;
			size--;
		} else {
			//如果entry的对象没有被GC回收
			//重新去计算下这个位置下的key的hash
			int h = k.threadLocalHashCode & (len - 1);
			//如果占的位置不是它hash的位置
			if (h != i) {
				//把现在的位置设置为null
				tab[i] = null;
				// Unlike Knuth 6.4 Algorithm R, we must scan until
				// null because multiple entries could have been stale.
				//看该有的位置是不是空的,如果不是,去找寻下一个null的(开放寻址解决hash冲突)
				while (tab[h] != null)
					h = nextIndex(h, len);
					//放到该有的位置去
					tab[h] = e;
			}
		}
	}
	//返回i的值 传进来的下标的 后面的最接近null的entry
	return i;
}

cleanSomeSlots方法:

代码语言:javascript
复制
//i 传入下标 n为传进来的数组的长度
private boolean cleanSomeSlots(int i, int n) {
	boolean removed = false;
	Entry[] tab = table;
	int len = tab.length;
	do {
		//根据传进来的下标去找下标后一个
		i = nextIndex(i, len);
		//得到该下标的enrty
		Entry e = tab[i];
		//如果下标的enrty 的key被GC回收了
		if (e != null && e.get() == null) {
			//n改为table的长度
			n = len;
			removed = true;
			//拿到i去清除与重新rehash后面的,直到找到null为止
			i = expungeStaleEntry(i);
		}
	} while ( (n >>>= 1) != 0); //不用遍历n次,只遍历n/2次,达到时间与空间的平衡
		return removed; //如果有清除,设置为true
}

前面分析了set方法第一次初始化ThreadLocalMap的过程,也对ThreadLocalMap的结构有了一个全面的了解。那么接下来看一下map不为空时的执行逻辑

  • 根据key的散列哈希计算Entry的数组下标
  • 通过线性探索探测从i开始往后一直遍历到数组的最后一个Entry
  • 如果map中的key和传入的key相等,表示该数据已经存在,直接覆盖
  • 如果map中的key为空,则用新的key、value覆盖,并清理key=null的数据
  • rehash扩容

3为什么Key要弱引用?

假如每个key都强引用指向ThreadLocal的对象,也就是上图虚线那里是个强引用,那么这个ThreadLocal对象就会因为和Entry对象存在强引用关联而无法被GC回收,造成内存泄漏,除非线程结束后,线程被回收了,map也跟着回收。

如果key是强引用,那么当我们执行threadLocal=null时,这个对象还被key关联,无法进行回收,只有当线程结束后,才会取消关联

但是用弱引用,我们就能在GC的时候,回收!

但是如果用的是线程池,那么的话线程就不会结束,只会放在线程池中等待下一个任务,但是这个线程的 map 还是没有被回收,它里面存在value的强引用,所以会导致内存溢出。

所以一般用threadLocal.remove()来清除内存 在ThreadLocal的生命周期中,都存在这些引用。看下图:实线代表强引用,虚线代表弱引用。

在这里插入图片描述
在这里插入图片描述

4Value为什么不用弱引用

是因为不清楚这个Value 除了map 的引用还是否还存在其他引用,如果不存在其他引用,当GC 的时候就会直接将这个Value干掉了,而此时我们的ThreadLocal还处于使用期间,就会造成Value为null的错误,所以将其设置为强引用

怎么解决hash冲突

1.首先,那个魔数就能保证重复性会低,但是基数必须是2的N次方(举例) 2.用开放寻址法,如果真的查到的下标已经存在数据,就去找下一个,找到一个null的为止,并且是环形查找,因为肯定会有空的,会进行提前扩容

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 1ThreadLocal使用
  • 2ThreadLocal原理源码分析
    • 2.1get方法
      • 2.1.1入口
      • 2.1.2 初始化方法
    • 2.2Set方法
      • 2.2.1.set入口
    • 2.3remove方法
      • 2.4 扩容逻辑
        • 2.5其他非正常情况
          • 2.5.1当ThreadLocal与ThreadLocal1的hash值冲突
          • 2.5.2当Key被GC回收处理
      • 3为什么Key要弱引用?
      • 4Value为什么不用弱引用
      • 怎么解决hash冲突
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档