ThreadLocal的使用场景及实现原理
1. 什么是ThreadLocal?
线程局部变量(通常,ThreadLocal变量是private static修饰的,此时ThreadLocal变量相当于成为了线程内部的全局变量)
2. 使用场景
变量在线程内部共享,线程间无关
再具体点,可以分为两类:
- 单例的对象中static属性,线程内共享,线程间无关;
- 工具类属性,线程内共享,线程间无关。
为什么这么说呢?下面看4个问题:
(1)对象为什么要是单例的?
如果对象不是单例的,那么大可以每次都new一个对象,然后对用到属性赋值就行,代码如下:
public class Service {
private String key;
void A() {
// 代码实现,中间用到key
}
void B() {
// 代码实现,中间用到key
}
// 省略get和set方法
}
在使用时,每个线程都new Service(),并对key赋值,然后调用其中的方法就行了,保证方法A和B用的key都是一个值。
(2)单例对象的属性共享
如果希望单例对象中的某个属性可以被共享,那么将属性声明为static就行了:
public class Service {
private static String key;
// 省略其他方法
}
上面的实现确实保证了所有方法都能使用key,然而,在多线程环境下,key是不安全的。
(3)单例对象在线程内属性共享,不同线程间相互不影响
这就轮到ThreadLocal上场了:
public class Service {
private static ThreadLocal<String> key = new ThreadLocal<String>() {
protected String initialValue() {
return Thread.currentThread().getName();
}
};
public void A() {
System.out.println("methodA: " + key.get());
key.set("methodA: " + key.get());
}
public void B() {
System.out.println("methodB: " + key.get());
}
}
使用方式:
public class ThreadLocalTest {
public static void main(String[] args) {
final Service service = new Service(); //模拟单例对象的使用
new Thread(new Runnable() {
@Override
public void run() {
service.A();
service.B();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
service.A();
service.B();
}
}).start();
}
}
运行结果:
methodA: Thread-0
methodB: methodA: Thread-0
methodA: Thread-1
methodB: methodA: Thread-1
(4)工具类中线程共享,线程间无关
工具类的代码:
public final class XUtil {
private static ThreadLocal<String> key = new ThreadLocal<String>();
private XUtil() {
}
public static void A() {
// 实现
}
public static void B() {
// 实现
}
}
在使用XUtil时,每个线程中key可以使用,不同线程间不受影响。
3. ThreadLocal的实现原理
为什么一个static的变量(即:类变量)可以做到:线程内共享,线程间无关?
看下ThreadLocal中的get源码:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map!=null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if(e!=null)
return (T)e.value;
}
return setInitialValue();
}
关键就在getMap()方法:
ThreadLocalMap getMap(Thread t) {
return t.threadlocals;
}
取的是当前线程内部的threadLocals属性。
查看Thread类:
ThreadLocal.ThreadLocalMap threadLocals = null;
threadLocals是ThreadLocal类中自定义的一个HashMap类。
原来数据就存在当前线程内部,自然就能做到线程内共享,线程间无关了。
接着看下set的源码:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap();
if(map != null)
map.set(this, value);
else
createMap(t,value);
}
无论是map.set(this, value)还是createMap(t, value),最后都是将数据保存到当前线程中的那个HashMap中:将ThreadLocal变量作为key,value就是要保存的数据。
4. ThreadLocal的内存泄露
在前面看到数据最终是存在线程内部的一个Map中的:
ThreadLocal.ThreadLocalMap threadLocals = null;
且key是ThreadLocal变量的引用,在get方法:
ThreadLocalMap.Entry e = map.getEntry(this); // this为当前对象的引用
当ThreadLocal变量被销毁时,而当前线程又持有ThreadLocal的引用,那么ThreadLocal就不会被回收,导致内存泄露。
然而,编写JDK的大牛们考虑到了这个问题,因此将ThreadLocalMap的key设置为弱引用:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
}
对ThreadLocal变量的弱引用,在GC时,ThreadLocal变量就会被回收。
于是,在当前线程的本地变量HashMap中,原来ThreadLocal作为key的,现在变成null作为key了,该key-value变得不可访问了,如果当前线程一直不结束,那么value对应的对象就无法释放,也就是发生内存泄露了。
参考文献:
作者: Acode
出处: http://www.cnblogs.com/acode/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接 如有问题, 可留言咨询.