1. 面试的问法
- 知道ThreadLocal吗?
- 说说你对ThreadLocal的理解
- 也有面试官会慢慢引导到这个话题上,比如提问“在多线程环境下,如何防止自己的变量被其它线程篡改”,将主动权交给你自己,剩下的靠自己发挥。
2. ThreadLocal的作用
本质上,ThreadLocal是通过空间换时间,从而实现每个线程当中都会有一个变量的副本,这样每个线程都会操作各自的副本, 从而完全规避了多线程的并发问题。
3. 基本使用
ThreadLocal通常的使用方法是:将它声明为一个类的静态变量,相当于是一个全局变量。每个线程访问它时都会有自己的副本。
public class MyTest3 { |
4. 底层原理
4.1. 为什么ThreadLocalMap的key使用的是弱引用?
为什么②使用的是弱引用?ThreadLocalMap.Entry继承了WeakReference,然后key是弱引用
如果stack上的threadLocal引用没有用了,按理说ThreadLocal对象就不应该存在,需要被回收掉。那如果②是一个强引用,那么它会始终指向这个TL对象,从而就无法被GC回收。
相反,②是弱引用,下次GC一定会回收这个key指向的TL对象。所以,使用弱引用是为了避免内存泄漏。
key被回收了,设置成了null,那value呢?
实际上TL的get/set/remove方法,本身会去检测那些key为null的entry,然后释放value对象。
threadLocalMap对象是定义在Thread类的,在线程exit时,会释放这个引用,回收这个map (Thread #exit)
4.2. 是否有内存泄露问题,怎么样避免?
如果使用不当,依然会有可能造成内存泄露。TL变量的生命周期,一般是整个线程,如果是在线程池中运行,线程没有及时释放,TL对象保存的对象又比较大,
那么会造成大对象一直存在于内存中,无法被GC线程回收掉。
解决: 程序中,当一个TL对象没有用后,应该手动调用remove方法避免内存泄漏,并且是在finally块调用它。
4.3. ThreadLocal变量的生命周期
Thread的exit方法,会给map引用设置为null。那么使用线程池的情况呢?
5. 源码阅读 和 验证
从get方法开始,set方法。引入内部类:ThreadLocalMap, Entry。
- Thread #threadLocals (ThreadLocal.ThreadLocalMap) – 每个线程对象,里面都有一个map (TLM), map的Entry是一个key-value结构,key是:ThreadLocal对象,value是程序中设置的对象类型(String等)
- 在第一次调用get时,如果没有调用过set方法,那么取得的就是默认值 (null)
threadLocal.set("Hello");
set实际上是把 (threadLocal, “Hello”) 这样一个key-value的Entry放到当前线程的ThreadLocalMap中去。- Thread类 和 ThreadLocal类 是通过ThreadLocal的内部类ThreadLocalMap关联在一起的
- ThreadLocalMap #Entry 继承自WeakReference,为什么?
避免内存泄露,但是如果代码写得不好,依然可能会泄露
6. SimpleDateFormat
SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。
说明:如果是JDK8的应用,可以使用instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat,
官方给出的解释:simple beautiful strong immutable thread-safe。
Positive example 1:
private static final String FORMAT = "yyyy-MM-dd HH:mm:ss";
public String getFormat(Date date){
SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT);
return sdf.format(date);
}Positive example 2:
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public void getFormat(){
synchronized (sdf){
sdf.format(new Date());
….;
}Positive example 3:
private static final ThreadLocal<DateFormat> DATE_FORMATTER = new ThreadLocal<DateFormat>() {
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};