简单的使用
是什么
ThreadLocal是一个线程的局部变量,只有当前线程可以访问,因为只有当前线程可以访问,所以是线程安全的。
示例
举例
多线程环境中使用SimpleDateFormat 解析日期代码
public class SimpleDateFormatTest {
// SimpleDateFormat 实例 private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 解析日期任务类
public static class ParseDate implements Runnable {
private int i = 0;
public ParseDate(int i) {this.i = i;}
public void run() {
try {
Date date = sdf.parse("2020-10-06 19:21:" + i % 60);
System.out.println(i + " : " + date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 创建线程池,解析日期
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
pool.execute(new ParseDate(i));
}
}执行结果
Exception in thread "pool-1-thread-2" Exception in thread "pool-1-thread-1" java.lang.NumberFormatException: multiple points
可以看到执行后抛出异常,所以SimpleDateFormat.parse()方法不是线程安全的
解决方案
方案一:在parse方法前后加锁(这里不讨论)
方案二:使用ThreadLocal,代码如下:
1 | public class x02ParseDate { |
为每一个线程分配不同的对象,需要在应用层进行保证;ThreadLocal只是起到了容器的作用
实现原理
set() 方法
我们来看看 set() 方法的内部
1 | public void set(T value) { |
可以简单地把ThreadlLocalMap看作是一个Map(但请注意,它不是HashMap,而是存储 key-value 结构的 Entry)。
ThreadlLocalMap 是定义在Thread类中地成员,如下代码所示:
1 | ThreadLocal.ThreadLocalMap threadLocals = null; // Thread 类中 |
我们设置的数据写入了 threadLocals 中,其中 key 为 ThreadLocal 当前对象,value 就是我们设置的值。threadLocals 保存了当前自己线程所有的“局部变量”,也就是一个ThreadLocal变量的集合。
get() 方法
1 | public T get() { |
ThreadLocal 中的一些变量是维护在Thread类内部的,所以:只要线程不退出,对象的引用就一直存在,无法被GC回收。
当线程退出时,Thread类会进行一些清理操作(包括清理ThreadLocalMap)。通过将引用置为null,使得JVM将其当作垃圾并进行回收,详细内容见下小节.
ThreadLocal 导致内存泄漏
- 问题
如上面所说,线程不退出,对象引用就一直存在,这在线程池中可能会导致内存泄漏。
例如固定大小的线程池(FixThreadPool),其线程总是存在。如果将一些大对象设置进了 ThreadLocal 中,使用了几次后就不再使用,同时也没有清理它,这会导致大对象无法被回收,最终导致内存泄露。 - 如何解决
- 使用 ThreadLocal.remove() 方法移除这个变量
- 使用类似于 obj = null 的代码
- *为何使用类似于 obj = null 的代码可以防止内存泄漏**
ThreadLocalMap使用了弱引用(弱引用就是:在垃圾回收时,JVM 发现弱引用,就直接将其回收)。
ThreadLocalMap 内部由一系列 Entry 构成,每个Entry都继承了弱引用 WeakRefefence,如下代码所示:
1 | static class Entry extends WeakReference<ThreadLocal<?>> { |
其中 k 是 ThreadLocal 实例,作为弱引用使用。super(k)就是调用了 WeakReference 的构造函数;
因此虽然使用 ThreadLocal 作为 Entry 的 k,但实际上它并不持有 ThreadLocal 的引用。
当 ThreadLocal 的外部强引用被回收时,Entry 中的 k 就会变成 null。
当系统进行ThreadLocalMap清理时(比如将新的变量加入表中,就会进行一次清理,虽然JDK不一定会进行一次彻底的扫描),就会将这些垃圾数据回收。ThreadLocal的回收机制如图所示: