1、概述
ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
2、原理
事实上通过查看ThreadLocal的源码可以发现ThreadLocal中有一个静态内部类ThreadLocalMap。
ThreadLocalMap还有一个静态内部类Entry其实保存的Object value就是ThreadLocal中保存的局部变量,static class Entry extends WeakReference
而在Thread类中包含了ThreadLocal.ThreadLocalMap threadLocals = null 这个属性,即每个线程自己维护自己的ThreadLocalMap。
所以调用threadLocal.get()方法实际上就是先通过Thread.currentThread()获取当前线程后,再获取ThreadLocalMap,最后再将threadLocal作为key获取Entry后获取value。
ThreadLocalMap的成员是一个Entry[] table数组,目的就是为了保存多个ThreadLocal变量保存的值,采用线性探测避免冲突。
set同理。
threadLocal.get() -> (Thread.currentThread()) thread -> (ThreadLocal.ThreadLocalMap threadLocals是thread的一个成员变量) thread.threadLocals -> (threadLocals.getEntrythreadLocal()) entry -> entry.value
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();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
3、内存泄露
threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法.
在threadlocal的生命周期中,都存在这些引用. 看下图: 实线代表强引用,虚线代表弱引用.
每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal. 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.
所以得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。如果线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。
Java为了最小化减少内存泄露的可能性和影响,在ThreadLocal的get,set的时候都会清除线程Map里所有key为null的value。但是还是会出现这种情况:threadLocal对象设null了,开始发生“内存泄露”,然后使用线程池,这个线程结束,线程放回线程池中不销毁,这个线程一直不被使用,或者分配使用了又不再调用get,set方法,那么这个期间就会发生真正的内存泄露。
4、InheritableThreadLocal
http://blog.csdn.net/ni357103403/article/details/51970748
InheritableThreadLocal 主线程在创建新线程的时候能够使新线程继承主线程的InheritableThreadLocal 内保存的值。
5、弱引用
java语言中为对象的引用分为了四个级别,分别为 强引用 、软引用、弱引用、虚引用。
本文只针对java中的弱引用进行一些分析,如有出入还请多指正。
在分析弱引用之前,先阐述一个概念:什么是对象可到达和对象不可到达状态。
其实很简单,我举个例子:
现在有如下两个类class A class B,在JVM上生成他们两个类的实例分别为 instance a instance b
有如下表达式:
A a = new A();
B b = new B();
两个强引用对象就生成了,好吧,那么这个时候我做一下修改:
A a = new A();
B b = new B(a);
B的默认构造函数上是需要一个A的实例作为参数的,那么这个时候 A和B就产生了依赖,也可以说a和b产生了依赖,
我们再用一个接近内存结构的图来表达:
a是对象A的引用,b是对象B的引用,对象B同时还依赖对象A,
那么这个时候我们认为从对象B是可以到达对象A的。
于是我又修改了一下代码
A a = new A();
B b = new B(a);
a = null;
A对象的引用a置空了,a不再指向对象A的地址,
我们都知道当一个对象不再被其他对象引用的时候,是会被GC回收的,很显然及时a=null,那么A对象也是不可能被回收的,因为B依然依赖与A,在这个时候,造成了内存泄漏!
那么如何避免上面的例子中内存泄漏呢?
很简单:
A a = new A();
B b = new B(a);
a = null;
b = null;
这个时候B对象再也没有被任何引用,A对象只被B对象引用,尽管这样,GC也是可以同时回收他们俩的,因为他们处于不可到达区域。
弱引用来了!
A a = new A();
WeakReference wr = new WeakReference(a);
//B b = new B(a);
当 a=null ,这个时候A只被弱引用依赖,那么GC会立刻回收A这个对象,这就是弱引用的好处!他可以在你对对象结构和拓扑不是很清晰的情况下,帮助你合理的释放对象,造成不必要的内存泄漏!!
-SoftReference
soft reference和weak reference一样, 但被GC回收的时候需要多一个条件: 当系统内存不足时(GC是如何判定系统内存不足? 是否有参数可以配置这个threshold?), soft reference指向的object才会被回收. 正因为有这个特性, soft reference比weak reference更加适合做cache objects的reference. 因为它可以尽可能的retain cached objects, 减少重建他们所需的时间和消耗.
Java弱引用(WeakReference)的理解与使用