ThreadLocal需要解决的问题是什么
- ThreadLocal被用来“解决多线程并发”问题
- ThreadLocal与同步机制的区别
- 同步机制:对于多线程访问的共享资源,通过“锁”对变量进行限制,此时变量只有一份;
- ThreadLocal:在多线程环境下,每个线程拷贝一份变量副本,各个线程使用自己的那一份(即所谓的Local),彼此之间互不影响;
ThreadLocal如何解决多线程并发问题
以下代码基于JDK1.8版本
- 既然每个线程都会拷贝一份变量,那么我们先看一下这个变量会存在哪里。首先来看Thread类:
public class Thread implements Runnable {
//......
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//......
}
显然,变量的存储肯定与ThreadLocalMap有关
2.ThreadLocal类的ThreadLocalMap:
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;
}
}
首先,ThreadLocalMap是一个静态内部类;其次,通过WeakReference<ThreadLocal<?>>的修饰,ThreadLocal变为一个弱引用指向类;再次,还有一个静态内部类Entry存储键值,其中Key为弱引用的ThreadLocal本身(通过super(k)实例化)。
再看一下ThreadLocalMap的内部函数:
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);
}
先初始化一个长度为16的Entry数组,然后通过哈希算法计算出键值的映射关系,再存到数组中就OK了。**顺带一提,这里是一个无锁并发的哈希实现,具体可以参考[世界上最简单的无锁哈希表](http://blog.jobbole.com/39186/)**
对于其他的方法,暂时省略......
ThreadLocal内存泄漏问题
为什么ThreadLocal会产生内存泄漏
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。
但是这些被动的预防措施并不能保证不会内存泄漏:
使用线程池的时候,这个线程执行任务结束,ThreadLocal对象被回收了,线程放回线程池中不销毁,这个线程一直不被使用,导致内存泄漏。
分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么这个期间就会发生内存泄漏。
如何避免ThreadLocal内存泄漏
每次使用完ThreadLocal,都要记得手动调用remove()方法,清除数据。
下一篇介绍ThreadLocal的实战应用与扩展。