目录
Thread源码
ThreadLocal源码
TreadLocal和Synchronized
ThreadLocal原理
ThreadLocal引发的内存泄漏分析
java.lang.ThreadLocal.ThreadLocalMap,ThreadLocal中get/set方法都是操纵的Thread类的threadLocals变量,Thread源码如下:
public class Thread implements Runnable {
/** 这里只看一些 常见的参数 */
/** 线程名 */
private volatile char name[];
/** 优先级 */
private int priority;
/** 是否为守护线程 */
private boolean daemon;
/** 线程要执行的目标任务 */
private Runnable target;
/** 所属线程组 */
private ThreadGroup group;
/** 类加载器 */
private ClassLoader contextClassLoader;
/**
* ThreadLocal 能为线程设置线程私有变量 就是通过下面这个threadLocals变量完成的,
* ThreadLocal的get/set方法就是通过操作 各个线程的 threadLocals 变量实现的。
* 1、线程A持有一个 ThreadLocalMap 变量;
* 2、线程A调用一个类的 ThreadLocal变量 tlA 的 get/set方法;
* 3、tlA(ThreadLocal)的 get/set方法 获取当前线程A,调用 线程A 的 ThreadLocalMap变量 的get/put方法;
* 4、其它线程 调用 tlA(ThreadLocal)的 get/set方法 同理。
*/
ThreadLocal.ThreadLocalMap threadLocals;
ThreadLocal.ThreadLocalMap inheritableThreadLocals;
/** 线程栈的大小 */
private long stackSize;
/**
* Thread类定义了6个线程状态:New、Runnable、Blocked、Waiting、TimedWaiting、Terminated(终止)
* 实际上还会把 Runnable 再细分为 就绪(未抢到时间片) 和 运行中(抢到时间片)
*/
private volatile int threadStatus;
/** 最小优先级 */
public static final int MIN_PRIORITY = 1;
/** 中等优先级 */
public static final int NORM_PRIORITY = 5;
/** 最大优先级 */
public static final int MAX_PRIORITY = 10;
/**
* 内部枚举类,用来描述线程状态,状态值有:
* NEW: 新建,还未调用start()方法;
* RUNNABLE: 运行,在java多线程模型中,就绪和运行都是运行状态;
* BLOCKED: 阻塞;
* WAITING: 等待,需要其他的线程来唤醒;
* TIMED_WAITING:超时等待,可以在指定的时间内自动醒来,如 sleep()方法;
* TERMINATED: 终止,线程执行完毕。
*/
public static final class State extends Enum {
public static final State NEW;
public static final State RUNNABLE;
public static final State BLOCKED;
public static final State WAITING;
public static final State TIMED_WAITING;
public static final State TERMINATED;
private static final State VALUES[];
static {
NEW = new State("NEW", 0);
RUNNABLE = new State("RUNNABLE", 1);
BLOCKED = new State("BLOCKED", 2);
WAITING = new State("WAITING", 3);
TIMED_WAITING = new State("TIMED_WAITING", 4);
TERMINATED = new State("TERMINATED", 5);
VALUES = (new State[] { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED });
}
private State(String s, int i) {
super(s, i);
}
}
/**
* 一系列 构造方法 ------------------------------------------------------
* 可以看出来,其中都调用了init()方法,这也是一个约定俗成的规矩, 即,如果要在 new 时进行一些初始化操作,
* 那么请将初始化操作单独写在 init()方法中,然后在构造函数中调用该 init()方法
*/
public Thread() {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(null, null, (new StringBuilder()).append("Thread-").append(nextThreadNum()).toString(), 0L);
}
public Thread(Runnable runnable) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(null, runnable, (new StringBuilder()).append("Thread-").append(nextThreadNum()).toString(), 0L);
}
Thread(Runnable runnable, AccessControlContext accesscontrolcontext) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(null, runnable, (new StringBuilder()).append("Thread-").append(nextThreadNum()).toString(), 0L,
accesscontrolcontext);
}
public Thread(ThreadGroup threadgroup, Runnable runnable) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(threadgroup, runnable, (new StringBuilder()).append("Thread-").append(nextThreadNum()).toString(), 0L);
}
public Thread(String s) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(null, null, s, 0L);
}
public Thread(ThreadGroup threadgroup, String s) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(threadgroup, null, s, 0L);
}
public Thread(Runnable runnable, String s) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(null, runnable, s, 0L);
}
public Thread(ThreadGroup threadgroup, Runnable runnable, String s) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(threadgroup, runnable, s, 0L);
}
public Thread(ThreadGroup threadgroup, Runnable runnable, String s, long l) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(threadgroup, runnable, s, l);
}
private void init(ThreadGroup threadgroup, Runnable runnable, String s, long l) {
init(threadgroup, runnable, s, l, null);
}
/**
* 初始化线程
*/
private void init(ThreadGroup threadgroup, Runnable runnable, String name, long l,
AccessControlContext accesscontrolcontext) {
// 参数校验,线程name不能为null
if (name == null)
throw new NullPointerException("name cannot be null");
this.name = name.toCharArray();
// 当前线程就是该线程的父线程
Thread parent = currentThread();
SecurityManager securitymanager = System.getSecurityManager();
if (threadgroup == null) {
if (securitymanager != null)
threadgroup = securitymanager.getThreadGroup();
if (threadgroup == null)
threadgroup = parent.getThreadGroup();
}
threadgroup.checkAccess();
if (securitymanager != null && isCCLOverridden(getClass()))
securitymanager.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
threadgroup.addUnstarted();
// 守护线程、优先级等设置为父线程的对应属性
group = threadgroup;
daemon = parent.isDaemon();
priority = parent.getPriority();
if (securitymanager == null || isCCLOverridden(parent.getClass()))
contextClassLoader = parent.getContextClassLoader();
else
contextClassLoader = parent.contextClassLoader;
inheritedAccessControlContext = accesscontrolcontext == null ? AccessController.getContext()
: accesscontrolcontext;
target = runnable;
setPriority(priority);
if (parent.inheritableThreadLocals != null)
// 创建线程共享变量副本
inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
stackSize = l;
// 分配线程id
tid = nextThreadID();
}
public synchronized void start() {
//假若当前线程初始化还未做好,不能start,0->NEW状态
if (threadStatus != 0)
throw new IllegalThreadStateException();
//通知group该线程即将启动,group的未启动线程数量减1
group.add(this);
boolean started = false;
try {
// 调用native的start0()方法 启动线程,启动后执行run()方法
start0();
started = true;
} finally {
try {
//启动不成功,group设置当前线程启动失败
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
public void run() {
if (target != null)
target.run();
}
/**
* 请求终止线程。interrupt不会真正停止一个线程,它仅仅是给这个线程发了一个信号,
* 告诉它要结束了,具体要中断还是继续运行,将由被通知的线程自己处理
*/
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0();
b.interrupt(this);
return;
}
}
interrupt0();
}
private native void interrupt0();
/**
* 线程main 调用了线程A的join方法,则 线程main 会被阻塞,直到线程A执行完毕
*/
public final void join() throws InterruptedException {
join(0);
}
/**
* 实际上是利用 wait/notify机制 来实现的
*/
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
// millis 为 0,所以走这个分支
if (millis == 0) {
// 当前线程是否还在运行,还在运行 则main线程 进入等待状态,直到 A线程运行完毕,将其唤醒
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
/**
* 线程睡眠指定的时间,释放CPU资源,但不释放锁
*/
public static native void sleep(long millis) throws InterruptedException;
/**
* 线程是否还在运行
*/
public final native boolean isAlive();
}
ThreadLocal类提供了get/set线程局部变量的实现,ThreadLocal成员变量与正常的成员变量不同,每个线程都可以通过ThreadLocal成员变量get/set自己的专属值。ThreadLocal实例通常是类中的私有静态变量。在类中定义ThreadLocal变量时,一般在定义时就进行实例化。
public class ThreadLocal {
/**
* ThreadLocal能为每个 Thread线程 绑定一个专属值的奥秘就是:
* 每个Thread对象都持有一个 ThreadLocalMap类型的成员变量,其key为ThreadLocal对象,
* value为绑定的值,所以每个线程调用 ThreadLocal对象 的set(T value)方法时,都会将
* 该ThreadLocal对象和绑定的值 以键值对的形式存入当前线程,这样,同一个ThreadLocal对象
* 就可以为每个线程绑定一个专属值咯。
* 每个线程调用 ThreadLocal对象的get()方法时,就可以根据 当前ThreadLocal对象 get到 绑定的值。
*/
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程对象中持有的 ThreadLocalMap类型的成员变量
// ThreadLocalMap,看名字也知道它是一个 Map类型的 类
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
// 经过前面对 Thread类 源码的分析,可以知道,Thread类中有一个 ThreadLocalMap 类型的
// threadLocals变量
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 通过当前 ThreadLocal对象,获取绑定的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void remove() {
// 获取当前线程的ThreadLocalMap成员变量,不为空就将当前 ThreadLocal对象
// 对应的 键值对 remove掉
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
/**
* 与大部分 Map 的实现相同,底层也是使用 动态数组来保存 键值对Entry,也有rehash、resize等
* 操作
*/
static class ThreadLocalMap {
/**
* 存储键值对,key 为 ThreadLocal对象,value 为 与该ThreadLocal对象绑定的值
* Entry的key是对ThreadLocal的弱引用,当抛弃掉ThreadLocal对象时,垃圾收集器会
* 忽略这个key的引用而清理掉ThreadLocal对象,防止了内存泄漏
*/
static class Entry extends WeakReference> {
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
// 看过 HashMap 或 ConcurrentHashMap 源码的同学 一定下面对这些代码很眼熟
/**
* 数组初始容量
*/
private static final int INITIAL_CAPACITY = 16;
/**
* Entry数组,用于存储 k, Object v>键值对
*/
private Entry[] table;
/**
* Entry元素数量
*/
private int size = 0;
/**
* 类似于 HashMap 扩容因子机制
*/
private int threshold; // Default to 0
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/**
* 系列构造方法
*/
ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal
ThreadLocal不是用来解决线程安全问题的,多线程不共享,不存在竞争,其目的是使线程能够使用本地变量。项目如果使用了线程池,那么线程回收后ThreadLocal变量要remove掉,否则线程池回收线程后,变量还在内存中,消耗资源或者会引起其他意向不到的问题。例如Tomcat容器的线程池,可以在拦截器中处理,继承HandlerInterceptorAdapter然后复写afterCompletion()方法,remove掉变量。
ThreadLocal和Synchronized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的差别。synchronized是利用锁的机制,使变量或代码块在某一时间仅仅能被一个线程訪问。而 ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。
使用场景
当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以采用 ThreadLocal。比如对于Handler来说,它需要获取当前线程的Looper,Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可实现Looper在线程中的存取。
复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口的多样性,在这种情况下,还需要监听器能够贯穿整个线程的执行过程,这时可以采用ThreadLocal,采用ThreadLocal可以让监听器作为线程内的全局对象而存在,在线程内部只要通过get方法就可以获取到监听器。
Spring多数据源配置的切换、Spring事务注解的实现、日志框架Slf4j中MDC类的实现、存储用户Session等。
ThreadLocal类中4个基本方法:
public class ThreadLocal {
void set(Object value)
//设置当前线程的线程局部变量的值。
public T get()
//该方法返回当前线程所对应的线程局部变量。
public void remove()
//将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected T initialValue()
//返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() 或 set(Object)时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一 个null。
}
首先定义一个ThreadLocal对象,选择Boolean类型,然后分别在主线程、子线程1和子线程2中设置和访问它的值。
public class test {
public static void main(String[] args) {
ThreadLocal mThreadLocal = new ThreadLocal<>();
mThreadLocal.set(true);
System.out.println("[Thread#main]threadLocal=" + mThreadLocal.get());
new Thread() {
@Override
public void run() {
super.run();
mThreadLocal.set(false);
System.out.println("[Thread#1]threadLocal=" + mThreadLocal.get());
}
}.start();
new Thread() {
@Override
public void run() {
super.run();
System.out.println("[Thread#2]threadLocal=" + mThreadLocal.get());
}
}.start();
}
运行后打印信息:
[Thread#main]threadLocal=true
[Thread#1]threadLocal=false
[Thread#2]threadLocal=null
虽然在不同的线程中访问的是同一个ThreadLocal对象,但是通过ThreadLocal获取到的值是不一样的。首先看ThreadLocal的set方法:
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
上面的set方法,首先会获取当前线程,通过getMap(Thread t) 来获取ThreadLocalMap ,如果这个map不为空的话,就将ThreadLocal和要存放的value设置进去,否则就创建一个ThreadLocalMap然后再设置。
/**
* 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;
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap是ThreadLocal里面的静态内部类,每一个Thread都有一个对应的 ThreadLocalMap,所以getMap是直接返回Thread的成员,在Thread类中有一个成员专门用于存储线程的ThreadLocal的数据如下所示 :
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class.
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
}
在threadLocals内部有一个数组private Entry[] table,ThreadLocal的值就是存在这个table数组中,ThreadLocal的内部类ThreadLocalMap源码:
static class ThreadLocalMap {
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
}
可以看到有个Entry内部静态类,它继承了WeakReference,记录了两个信息一个是ThreadLocal类型,一个是Object类型的值。getEntry方法则是获取某个ThreadLocal对应的值,set方法就是更新或赋值相应的ThreadLocal对应的值。threadLocals是如何使用set方法将ThreadLocal的值存储到 table数组中的,如下所示:
/**
* 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();
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();
}
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();
}
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> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
拿到每个线程独有的ThreadLocalMap然后再用ThreadLocal的当前实例,拿到Map中的相应的 Entry,然后就可以拿到相应的值返回出去。如果Map为空,还会先进行map的创建,初始化等工作。取出当前线程的threadLocals对象,如果这个对象为null,就调用setInitialValue()方法。
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
setInitialValue()方法中,将initialValue()的值赋给我们想要的值,默认情况下initialValue()的值为 null,也可以重写这个方法。
protected T initialValue() {
return null;
}
从ThreadLocal的set()和get()方法可以看出,他们所操作的对象都是当前线程的threalLocals对象的 table数组,在不同的线程中访问同一个ThreadLocal的set()和get()方法时,对ThreadLocal所做的 读 / 写操作权限仅限于各自线程的内部,这就是为什么可以在多个线程中互不干扰地存储和修改数据。
ThreadLocal是线程内部的数据存储类,每个线程中都会保存一个ThreadLocal.ThreadLocalMap threadLocals = null;ThreadLocalMap是ThreadLocal的静态内部类,里面保存了一个private Entry[] table数组,这个数组就是用来保存ThreadLocal中的值。对ThreadLocal每次进行set、get、remove等操作都是对ThreadLocalMap进行操作,在多个线程中互不干扰地存储和修改数据,从而做到线程隔离。
引用Object o = new Object(),这个o是指对象引用,而new Object() 是指在内存中产生了一个对象实例。
当写下 o=null 时,只是表示o不再指向堆中object的对象实例,不代表这个对象实例不存在了。
强引用 就是指在程序代码之中普遍存在的,类似“Object obj=new Object()” 这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象实例。
软引用 用来描述一些还有用但并非必需的对象。对于软引用关联着的对象, 在系统将要发生内存溢出异常之前,将会把这些对象实例列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在 JDK 1.2 之后,提供了 SoftReference 类来实现软引用。
弱引用 用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱 引用关联的对象实例只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时, 无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。在 JDK 1.2 之 后,提供了 WeakReference 类来实现弱引用。
虚引用 也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象 实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用 来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象 实例被收集器回收时收到一个系统通知。
//ThreadLocal造成的内存泄漏
public class test {
private static final int TASK_LOOP_SIZE = 500;
final static ThreadPoolExecutor poolExecutor
= new ThreadPoolExecutor(5, 5,
1,
TimeUnit.MINUTES,
new LinkedBlockingQueue<>());
static class LocalVariable {
private byte[] a = new byte[1024 * 1024 * 5];/*5M大小的数组*/
}
final static ThreadLocal localVariable
= new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
/*5*5=25*/
for (int i = 0; i < TASK_LOOP_SIZE; ++i) {
poolExecutor.execute(new Runnable() {
public void run() {
//localVariable.set(new LocalVariable());//启用ThreadLocal
new LocalVariable();//创建5m的对象
System.out.println("use local varaible");
//localVariable.remove();
}
});
Thread.sleep(100);
}
System.out.println("pool execute over");
}
}
执行上面程序,堆内存大小设置为-Xmx256m,然后启用一个线程池,大小固定为5个线程,在每个任务中new出一个数组。可以看到内存的实际使用控制在25M左右,因为每个任务中会不断new 出一个5M的数组,5*5=25M,
启用了ThreadLocal以后:
再执行,看看内存情况:
最高峰的内存占用也在25M左右,和不加ThreadLocal表现一样。这就充分说明确实发生了内存泄漏。
每个Thread维护一个ThreadLocalMap,映射表的key是ThreadLocal实例本身,value是真正需要存储的Object,也就是说ThreadLocal本身并不存储值,它只是作为一个key来让线程从 ThreadLocalMap获取value。仔细观察ThreadLocalMap,这个map是使用ThreadLocal的弱引用作为Key的,弱引用的对象在GC时会被回收。当把ThreadLocal变量置为null以后,没有任何强引用指向ThreadLocal实例,所以ThreadLocal将会被GC回收。这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreadLocalMap -> Entry -> value,而这块value永远不会被访问到了,所以存在着内存泄露。只有当前thread结束以后,current thread就不会存在栈中,强引用断开, Current Thread、Map value将全部被GC回收。最好的做法是不在需要使用ThreadLocal变量后,都调用它的remove()方法,清除数据。
从表面上看内存泄漏的根源在于使用了弱引用,为什么使用弱引用而不是强引用?
key使用强引用:引用ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal的对象实例不会被回收,导致Entry内存泄漏。
key使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal的对象实例也会被回收。value在下一次 ThreadLocalMap调用 set,get,remove 都有机会被回收。
由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障。因此ThreadLocal内存泄漏的根源是由于ThreadLocalMap 的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
JVM利用设置ThreadLocalMap的Key为弱引用,来避免内存泄露。
JVM利用调用remove、get、set方法的时候,回收弱引用。
当ThreadLocal存储很多Key为null的Entry的时候,而不再去调用remove、 get、set方法,那么将导致内存泄漏。
使用线程池+ThreadLocal时需要注意,因为这种情况下,线程是一直在不断的重复运行的,从而value可能造成累积的情况。