用处
可以私有化存储线程的变量值
用法
static class ResourceClass {
public final static ThreadLocal<String> RESOURCE_1 =
new ThreadLocal<String>();
public final static ThreadLocal<String> RESOURCE_2 =
new ThreadLocal<String>();
}
static class A {
public void setOne(String value) {
ResourceClass.RESOURCE_1.set(value);
}
public void setTwo(String value) {
ResourceClass.RESOURCE_2.set(value);
}
}
static class B {
public void display() {
System.out.println(ResourceClass.RESOURCE_1.get()
+ ":" + ResourceClass.RESOURCE_2.get());
}
}
public static void main(String []args) {
final A a = new A();
final B b = new B();
for(int i = 0 ; i < 15 ; i ++) {
final String resouce1 = "线程-" + I;
final String resouce2 = " value = (" + i + ")";
new Thread() {
public void run() {
try {
a.setOne(resouce1);
a.setTwo(resouce2);
b.display();
}finally {
ResourceClass.RESOURCE_1.remove();
ResourceClass.RESOURCE_2.remove();
}
}
}.start();
}
}
运行结果
线程-4: value = (4)
线程-2: value = (2)
线程-5: value = (5)
线程-1: value = (1)
线程-0: value = (0)
线程-3: value = (3)
线程-6: value = (6)
线程-7: value = (7)
线程-10: value = (10)
线程-11: value = (11)
线程-9: value = (9)
线程-12: value = (12)
线程-13: value = (13)
线程-8: value = (8)
线程-14: value = (14)
可以看到线程是交替执行的,但是每个线程取到的值却没有乱,说明已经做到了线程的私有化。
实现原理
看ThreadLocal的set方法源码
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
if (map != null)
map.set(this, value);//以当前ThreadLocal对象为键,value为值存储到map中
else
createMap(t, value);
}
ThreadLocal的get方法源码
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//以当前ThreadLocal对象为键,获取到map中的对象
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
通过上述源码可以看到,实际上是每个Thread线程维护着一个ThreadLocalMap,而ThreadLocalMap中的一个键即是一个ThreadLocal,value是我们通过set方法传进去的参数。看下面的图会好理解一点
也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。
关于内存泄漏
为什么会造成内存泄漏
下面字字精华,请耐心读完
我们上面说了ThreadLocal 本身并不存储值,它只是作为一个 key保存到ThreadLocalMap中,但是这里要注意的是它作为一个key用的是弱引用(什么是强引用,软引用,弱引用,虚引用)本处不做阐述。
因为没有强引用链,弱引用在GC的时候可能会被回收。这样就会在ThreadLocalMap中存在一些key为null的键值对(Entry)。因为key变成null了,我们是没法访问这些Entry的,但是这些Entry本身是不会被清除的,为什么呢?因为存在一条强引用链。即线程本身->ThreadLocalMap->Entry也就是说,恰恰我们在使用线程池的时候,线程使用完了是会放回到线程池循环使用的。由于ThreadLocalMap的生命周期和线程一样长,如果没有手动删除对应key就会导致这块内存即不会回收也无法访问,也就是内存泄漏。
其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。但是这些举动不能保证内存就一定会回收,因为可能这条线程被放回到线程池里后再也没有使用,或者使用的时候没有调用其get(),set(),remove()方法。
为什么使用弱引用,内存泄漏是否是弱引用的锅?
下面我们分两种情况讨论:
(1)key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
(2)key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set、get、remove的时候会被清除。
因此:内存泄漏归根结底是由于ThreadLocalMap的生命周期跟Thread一样长。如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
如何避免内存泄漏
每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
注意:并不是所有使用ThreadLocal的地方,都在最后remove(),他们的生命周期可能是需要和项目的生存周期一样长的,所以要进行恰当的选择,以免出现业务逻辑错误!
写在最后
看来好像没有什么更好的办法能避免这个问题,小生不才,希望能有指正。
参考原文:https://blog.csdn.net/bntX2jSQfEHy7/article/details/78315161