大家好,我是捡田螺的小男孩。
无论是工作还是面试,我们都会跟ThreadLocal打交道,今天就跟大家聊聊ThreadLocal哈~
- ThreadLocal是什么?为什么要使用ThreadLocal
- 一个ThreadLocal的使用案例
- ThreadLocal的原理
- 为什么不直接用线程id作为ThreadLocalMap的key
- 为什么会导致内存泄漏呢?是因为弱引用吗?
- Key为什么要设计成弱引用呢?强引用不行?
- InheritableThreadLocal保证父子线程间的共享数据
- ThreadLocal的应用场景和使用注意点
- github地址,麻烦给个star鼓励一下,感谢感谢
- 公众号:捡田螺的小男孩(欢迎关注,干货多多)
ThreadLocal是什么?
ThreadLocal,即线程本地变量。如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是在操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了并发场景下的线程安全问题。
//创建一个ThreadLocal变量
static ThreadLocal<String> localVariable = new ThreadLocal<>();
复制代码
为什么要使用ThreadLocal
并发场景下,会存在多个线程同时修改一个共享变量的场景。这就可能会出现线性安全问题。
为了解决线性安全问题,可以用加锁的方式,比如使用synchronized 或者Lock。但是加锁的方式,可能会导致系统变慢。加锁示意图如下:
还有另外一种方案,就是使用空间换时间的方式,即使用ThreadLocal。使用threadLocal类访问共享变量时,会在每个线程的本地,都保存一份共享变量的拷贝副本。多线程对共享变量修改时,实际上操作的是这个变量副本,从而保证线性安全。
2. 一个threadLocal的使用案例日常开发中,ThreadLocal经常在日期转换工具类中出现,我们先来看个反例:
/**
* 日期工具类
*/
public class DateUtil {
private static final SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static Date parse(String dateString) {
Date date = null;
try {
date = simpleDateFormat.parse(dateString);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
复制代码
我们在多线程环境跑DateUtil这个工具类:
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i ) {
executorService.execute(()->{
System.out.println(DateUtil.parse("2022-07-24 16:34:30"));
});
}
executorService.shutdown();
}
复制代码
运行后,发现报错了:
如果在DateUtil工具类,加上ThreadLocal,运行则不会有这个问题:
/**
* 日期工具类
*/
public class DateUtil {
private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static Date parse(String dateString) {
Date date = null;
try {
date = dateFormatThreadLocal.get().parse(dateString);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i ) {
executorService.execute(()->{
System.out.println(DateUtil.parse("2022-07-24 16:34:30"));
});
}
executorService.shutdown();
}
}
复制代码
运行结果:
Sun Jul 24 16:34:30 GMT 08:00 2022
Sun Jul 24 16:34:30 GMT 08:00 2022
Sun Jul 24 16:34:30 GMT 08:00 2022
Sun Jul 24 16:34:30 GMT 08:00 2022
Sun Jul 24 16:34:30 GMT 08:00 2022
Sun Jul 24 16:34:30 GMT 08:00 2022
Sun Jul 24 16:34:30 GMT 08:00 2022
Sun Jul 24 16:34:30 GMT 08:00 2022
Sun Jul 24 16:34:30 GMT 08:00 2022
Sun Jul 24 16:34:30 GMT 08:00 2022
复制代码
刚刚反例中,为什么会报错呢?这是因为SimpleDateFormat不是线性安全的,它以共享变量出现时,并发多线程场景下即会报错。
为什么加了ThreadLocal就不会有问题呢?并发场景下,ThreadLocal是如何保证的呢?我们接下来看看ThreadLocal的核心原理。
3. ThreadLocal的原理3.1 ThreadLocal的内存结构图为了有个宏观的认识,我们先来看下ThreadLocal的内存结构图