ThreadLocal中的WeakReference
在一般的网站开发中,基于Java的Web 框架都使用了ThreadLocal来存储一些全局的参数,在拦截器\Filter中设置变量,让变量可以在任意地方被获取。
一早就了解到里面有用到WeakReference(弱引用),但对弱引用仅限于一种懵懂的概念,并且认为只要GC,弱引用的对象就被回收掉了,实际情况呢?

Thread对象有一个变量名为 threadLocals 的 ThreadLocalMap对象,这个类和HashMap类似,里面定义了一个Entry数组,不过这个Entry对象弱引用了ThreadLocal
/**
* 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); //调用父类构造函数,设置Reference的referent属性
value = v;
}
}
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
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(); 获取Reference的referent属性 if (k == key) {
e.value = value;
return;
} if (k == null) {
replaceStaleEntry(key, value, i); //清理掉陈旧Entry,这种Entry是由于ThreadLocal变量对象被回收后k==null造成的
return;
}
} tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
注意以下方法
if (k == null) {
replaceStaleEntry(key, value, i); //清理掉陈旧Entry,这种Entry是由于ThreadLocal变量对象被回收后k==null造成的
return;
}
这个地方其实也解释了通常所说的ThreadLocal变量可能导致内存溢出的问题:
Entry弱引用了ThreadLocal,因此Entry是否存活,不会影响ThreadLocal的生命周期,ThreadLocal在没有其他对象引用后被回收,但是它对应的Entry中的value实际还在被Entry引用,而这不是一个弱引用,如果不清掉掉Entry,value就被一直强引用,无法释放,那么就可能会内存溢出
如果一直没有ThreadLocal变量访问,并且线程一直存活,就不会清理陈旧Entry,value永远无法释放,这也是某种意义上的内存泄露
一直以来,我把WeakReference理解成:弱引用的对象一定会在下次GC时回收掉,按此推断,ThreadLocal变量是不太安全的,因为用着用着就可能被GC掉了。
但是如果ThreadLocal有这么严重的问题,谁会去用呢!
实际上对象B弱引用了A,如果A除了B以外,没有其他引用(强、软引用)时,才会把A GC调,这也是为什么ThreadLocal实际上是一个安全的操作。
Thread.threadLocals 是一个Map,每个Entry都弱引用了ThreadLocal对象
因此Thread.threadLocals对每个ThreadLocal对象都是弱引用关系
调用ThreadLocal.get()方法
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); 可以看到get实际上是获取当前线程的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
} /**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以理解为一个ThreadLocal实例,在多个线程都存在副本,并且在不同线程设置的值,都不会影响到其他线程,因为ThreadLocal实例是存储在当前Thread对象上的
也就是说,如果Thread死掉后,它和ThreadLocal对象也就没有任何关系了,这对我们的程序没有任何影响,因为我们也没有机会再取它在Thread中的副本了;
而ThreadLocal变量被设置为null后,虽然Thread中的Entry弱引用了ThreadLocal,即使Thread对象还在,当ThreadLocal对象没有被其他对象引用后,它就可以被GC调了,Thread对象产生了弱引用,不会影响到ThreadLocal变量的回收
其他知识点:
需要注意的是,当一个线程执行Thread.start()后,如果start()方法已经执行完毕了,虽然Thread对象还在,其实它的生命周期已经结束了,对应的ThreadLocal变量可以被回收
如果线程再次调用Thread.start()方法,会抛出异常,因此不用关注线程再次被启动,因为它已经无法再被启动了(PS:线程复用技术并非是重新start,而是在线程内执行多个任务)
以下情况ThreadLocal变量是永远不会回收的
public class FlagContext {
/**
* ThreadLocal变量
*/
private final static ThreadLocal<Boolean> tbFlag = new ThreadLocal<Boolean>();
}
FlagContext的静态变量引用了ThreadLocal实例tbFlag,tbFlag是个final 声明的变量,永远不会为空,因此,这种情况下,tbFlag对应的值,只会在线程生命周期结束,或调用tbFlag.remove()才会被回收!原因呢:
ThreadLocal永远被FlagContext强引用了,而Thread和ThreadLocal不管是否是弱引用,即使Thread中的Entry被回收了,也不会被GC掉!
ThreadLocal中的WeakReference的更多相关文章
- ThreadLocal中优雅的数据结构如何体现农夫山泉的广告语
本篇文章主要讲解 ThreadLocal 的用法和内部的数据结构及实现.有时候我们写代码的时候,不太注重类之间的职责划分,经常造出一些上帝类,也就是什么功能都往这个类里放.虽然能实现功能但是并不优雅且 ...
- juce 中的WeakReference分析
juce中的WeakReference设计得比较巧妙,巧妙就是使用delete之后就可以通知道WeakReference,原理其实也很间单,其实就是在对象里添加了一个子对象masterReferenc ...
- 正确理解ThreadLocal:ThreadLocal中的值并不一定是完全隔离的
首先再讨论题主的这个观点之前我们要明确一下ThreadLocal的用途是什么? ThreadLocal并不是用来解决共享对象的多线程访问问题. 看了许多有关ThreadLocal的博客,看完之后会给人 ...
- Java中关于WeakReference和WeakHashMap的理解
新美大的10月11日的笔试中有一道选择题,让选择函数返回结果,代码如下: private static String test(){ String a = new String("a&quo ...
- Java中的 WeakReference 和 SoftReference
我们知道Java语言中没有指针,取而代之的是引用reference.Java中的引用又可以分为四种:强引用,弱引用(WeakReference),软引用(SoftReference),虚引用(Phan ...
- 谈谈java中的WeakReference
Java语言中为对象的引用分为了四个级别,分别为 强引用 .软引用.弱引用.虚引用. 本文只针对java中的弱引用进行一些分析,如有出入还请多指正. 在分析弱引用之前,先阐述一个概念:什么是对象可到达 ...
- Android中的WeakReference 弱引用
WeakReference 弱引用 定义:弱引用,与强引用(我们常见的引用方式)相对:特点是:GC在回收时会忽略掉弱引用对象(忽略掉这种引用关系),即:就算弱引用指向了某个对象,但只要该对象没有被强引 ...
- 【转】Java中关于WeakReference和WeakHashMap的理解
新美大的10月11日的笔试中有一道选择题,让选择函数返回结果,代码如下: private static String test(){ String a = new String("a&quo ...
- 理解java中的ThreadLocal(转)
一.对ThreadLocal概述 JDK API 写道: 该类提供了线程局部 (thread-local) 变量.这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的 ...
随机推荐
- iOS之 重绘机制
最近在看Core Animation , 今天来谈谈CALayer 和 UIView 中的重绘的一些认识: 我们都知道UIView里面有个成员layer,利用这个这个layer我们可以设置一些圆角,阴 ...
- Vim 神器的打造方式
Vim 神器的打造方式 [字体:大 中 小] Vim 是一个上古神器,本篇文章主要持续总结使用 Vim 的过程中不得不了解的一些指令和注意事项,以及持续分享一个前端工作者不得不安装的一些插 ...
- C++ 标准库之iomanip
C++ 标准库之iomanip istream & istream::get(char *, int, char = '\n');istream & istream::getline( ...
- The Euler function(线性筛欧拉函数)
/* 题意:(n)表示小于n与n互质的数有多少个,给你两个数a,b让你计算a+(a+1)+(a+2)+......+b; 初步思路:暴力搞一下,打表 #放弃:打了十几分钟没打完 #改进:欧拉函数:具体 ...
- mongodb集群【】
参考 http://www.jianshu.com/p/2825a66d6aed http://www.cnblogs.com/huangxincheng/archive/2012/03/07/238 ...
- 请求返回时的Size/Content Time/Latency的区别
Size/Content: Size是响应头部和响应体结合起来的大小,Content是请求内容解码后的大小.进一步了解可以看这里Chrome Dev Tools - “Size” vs “Conten ...
- java中表示二进制、八进制、十进制、十六进制
1.进制 进制是一种记数方式 ,可以用有限的数字符号代表所有的数值.由特定的数值组成. 2.进制的表现形式 二进制: 由0和1两个数字组成. 八进制: 由0-7数字组成,为了区分与其他进制的数字区别, ...
- JDBC连接数据库的几种方法
一. 最古老的方法(通过 Driver 接口直接连接数据库) 首先创建一个 Driver 实现类的对象 Driver dirver = new com.mysql.jdbc.Driver(); 准备连 ...
- NoSQL:redis缓存数据库
一 Redis介绍 Redis和Memcached类似,也属于key-value nosql 数据库 Redis官网redis.io, 当前最新稳定版4.0.1 和Memcached类似,它支持存储的 ...
- 阿里云ecs初始化磁盘后远程连接不到服务器
阿里云初始化磁盘后远程连接不到服务器 报错: WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! ... 原因:阿里云ecs第一次链接服务器之后会在本地电 ...