一、ThreadLocal简介
ThreadLocal(线程本地)叫做线程变量,属于当前线程,该变量对其他线程而言是隔离的,也就是该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
- 同一个ThreadLocal所包含的对象,在不同的Thread中有不同的副本。
- 因为每个线程Thread内有自己的副本,该副本只能由当前Thread使用。
- 既然每个Thread有自己的实列副本,且其他Thread不可访问,那就不存在多线程间共享的问题。
二、开发中实际使用
1、拦截器中使用根据token 查询出用户信息后存储线程副本ThreadLocal。
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler )
throws Exception {
log.info("===================进入拦截器===================");
String token = request.getHeader("Authorization");
.....
LoginUser loginUser = (LoginUser)redisService.get(token.replace(CacheConstants.TOKEN_PREFIX,""));
if (loginUser != null) {
UserInfoUtil.setLoginUser(loginUser);
tokenService.refreshToken(loginUser);
}
return true;
}
2、怎么存储。
public class UserInfoUtil {
// 用户信息
private static ThreadLocal<LoginUser> loginUserThreadLocal = new ThreadLocal<>();
/**
* 设置当前线程登录用户信息
*/
public static void setLoginUser(LoginUser loginUser) {
loginUserThreadLocal.set(loginUser);
}
/**
* 设置当前线程登录用户信息
*/
public static LoginUser getLoginUser() {
LoginUser loginUser = loginUserThreadLocal.get();
return loginUser;
}
3、怎么使用
public Order saveOrder(Order order) throws Exception {
LoginUser loginUser = UserInfoUtil.getLoginUser(){
if(loginUser == null){
order.setCreateBy(loginUser.getName)
}
return order;
}
三、源码分析
public class ThreadLocalThread {
public static void main(String[] args) {
ThreadLocal<String> threadLocalName = new ThreadLocal<>();
ThreadLocal<String> threadLocalToken = new ThreadLocal<>();
threadLocalName.set("微信公众号:攻城狮小章鱼");
threadLocalToken.set("8fjkajfei3reajfoea3urejfejoier3ur");
System.out.println(threadLocalName.get());
}
}
1、set()方法、通过Thread.currentThread()获取当前线程t。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
2、调用getMap(t)获取当前线程的( ThreadLocalMap) map。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
3、t.threadLocals 是Thread的一个属性(ThreadLocal.ThreadLocalMap threadLocals = null;)返回的是一个ThreadLocalMap的值。
4、执行if (map != null) 判断是否为null 不为null直接调用set()方法存值,如果为null,调用createMap创建一个new ThreadLocalMap(this, firstValue); this是当前的(ThreadLocal 对象)threadLocalName,firstValue是(“微信公众号:攻城狮小章鱼”)
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];//ThreadLocal定义的一个初始容量 16
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//进行hash计算
table[i] = new Entry(firstKey, firstValue);
size = 1;//第一次创建的时候长度是1
setThreshold(INITIAL_CAPACITY);
}
5、Entry继承了WeakReference ,super(k)是一个弱引用,所以说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;
}
}
6、通过以上几步一个第一次value就成功的存到Thread的本地副本里了l
数据存储示列:

7、当一个线程有多个ThreadLocal怎么样存储。当再次调用set()方法时ThreadLocalMap不为空的时候调用map.set_(this, value)_。
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) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
8、多个ThreadLocal数据存储示列。

四、内存泄漏
1、使用ThreadLocal时,一般建议将其声明为static final的,避免频繁创建ThreadLocal实例。
2、尽量避免存储大对象,如果非要存,那么尽量在访问完成后及时调用remove()删除掉。
五、总结
ThreadLocal不是洪水猛兽,不要听到「内存泄漏」就不敢使用它,只要你规范化使用是不会有问题的。再者,就算你不规范使用,ThreadLocal也做出了很多努力来最大程度的帮你避免发生「内存泄漏」。
前面已经说过,由于Key是弱引用,因此ThreadLocal可以通过key.get()==null来判断Key是否已经被回收,如果Key被回收,就说明当前Entry是一个废弃的过期节点,ThreadLocal会自发的将其清理掉。
ThreadLocal会在以下过程中清理过期节点:
1、调用set()方法时,采样清理、全量清理,扩容时还会继续检查。
2、调用get()方法,没有直接命中,向后环形查找时(expungeStaleEntry())。
3、调用remove()时,除了清理当前Entry,还会向后继续清理。
版权归属

1275

被折叠的 条评论
为什么被折叠?



